home *** CD-ROM | disk | FTP | other *** search
/ PC go! 2018 January / PCgo 01-2018 CD-ROM Germany.iso / nw.pak / Unnamed File 005243.txt < prev    next >
Encoding:
Text File  |  2015-07-29  |  118.4 KB  |  4,111 lines

  1. "use strict";
  2. /*
  3.  * Copyright (C) 2012 Google Inc. All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions are
  7.  * met:
  8.  *
  9.  *     * Redistributions of source code must retain the above copyright
  10.  * notice, this list of conditions and the following disclaimer.
  11.  *     * Redistributions in binary form must reproduce the above
  12.  * copyright notice, this list of conditions and the following disclaimer
  13.  * in the documentation and/or other materials provided with the
  14.  * distribution.
  15.  *     * Neither the name of Google Inc. nor the names of its
  16.  * contributors may be used to endorse or promote products derived from
  17.  * this software without specific prior written permission.
  18.  *
  19.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23.  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25.  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29.  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30.  */
  31.  
  32.  
  33. /**
  34.  * @enum {number}
  35.  */
  36. var WeekDay = {
  37.     Sunday: 0,
  38.     Monday: 1,
  39.     Tuesday: 2,
  40.     Wednesday: 3,
  41.     Thursday: 4,
  42.     Friday: 5,
  43.     Saturday: 6
  44. };
  45.  
  46. /**
  47.  * @type {Object}
  48.  */
  49. var global = {
  50.     picker: null,
  51.     params: {
  52.         locale: "en-US",
  53.         weekStartDay: WeekDay.Sunday,
  54.         dayLabels: ["S", "M", "T", "W", "T", "F", "S"],
  55.         shortMonthLabels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"],
  56.         isLocaleRTL: false,
  57.         mode: "date",
  58.         weekLabel: "Week",
  59.         anchorRectInScreen: new Rectangle(0, 0, 0, 0),
  60.         currentValue: null
  61.     }
  62. };
  63.  
  64. // ----------------------------------------------------------------
  65. // Utility functions
  66.  
  67. /**
  68.  * @return {!boolean}
  69.  */
  70. function hasInaccuratePointingDevice() {
  71.     return matchMedia("(pointer: coarse)").matches;
  72. }
  73.  
  74. /**
  75.  * @return {!string} lowercase locale name. e.g. "en-us"
  76.  */
  77. function getLocale() {
  78.     return global.params.locale || "en-us";
  79. }
  80.  
  81. /**
  82.  * @return {!string} lowercase language code. e.g. "en"
  83.  */
  84. function getLanguage() {
  85.     var locale = getLocale();
  86.     var result = locale.match(/^([a-z]+)/);
  87.     if (!result)
  88.         return "en";
  89.     return result[1];
  90. }
  91.  
  92. /**
  93.  * @param {!number} number
  94.  * @return {!string}
  95.  */
  96. function localizeNumber(number) {
  97.     return window.pagePopupController.localizeNumberString(number);
  98. }
  99.  
  100. /**
  101.  * @const
  102.  * @type {number}
  103.  */
  104. var ImperialEraLimit = 2087;
  105.  
  106. /**
  107.  * @param {!number} year
  108.  * @param {!number} month
  109.  * @return {!string}
  110.  */
  111. function formatJapaneseImperialEra(year, month) {
  112.     // We don't show an imperial era if it is greater than 99 becase of space
  113.     // limitation.
  114.     if (year > ImperialEraLimit)
  115.         return "";
  116.     if (year > 1989)
  117.         return "(\u5e73\u6210" + localizeNumber(year - 1988) + "\u5e74)";
  118.     if (year == 1989)
  119.         return "(\u5e73\u6210\u5143\u5e74)";
  120.     if (year >= 1927)
  121.         return "(\u662d\u548c" + localizeNumber(year - 1925) + "\u5e74)";
  122.     if (year > 1912)
  123.         return "(\u5927\u6b63" + localizeNumber(year - 1911) + "\u5e74)";
  124.     if (year == 1912 && month >= 7)
  125.         return "(\u5927\u6b63\u5143\u5e74)";
  126.     if (year > 1868)
  127.         return "(\u660e\u6cbb" + localizeNumber(year - 1867) + "\u5e74)";
  128.     if (year == 1868)
  129.         return "(\u660e\u6cbb\u5143\u5e74)";
  130.     return "";
  131. }
  132.  
  133. function createUTCDate(year, month, date) {
  134.     var newDate = new Date(0);
  135.     newDate.setUTCFullYear(year);
  136.     newDate.setUTCMonth(month);
  137.     newDate.setUTCDate(date);
  138.     return newDate;
  139. }
  140.  
  141. /**
  142.  * @param {string} dateString
  143.  * @return {?Day|Week|Month}
  144.  */
  145. function parseDateString(dateString) {
  146.     var month = Month.parse(dateString);
  147.     if (month)
  148.         return month;
  149.     var week = Week.parse(dateString);
  150.     if (week)
  151.         return week;
  152.     return Day.parse(dateString);
  153. }
  154.  
  155. /**
  156.  * @const
  157.  * @type {number}
  158.  */
  159. var DaysPerWeek = 7;
  160.  
  161. /**
  162.  * @const
  163.  * @type {number}
  164.  */
  165. var MonthsPerYear = 12;
  166.  
  167. /**
  168.  * @const
  169.  * @type {number}
  170.  */
  171. var MillisecondsPerDay = 24 * 60 * 60 * 1000;
  172.  
  173. /**
  174.  * @const
  175.  * @type {number}
  176.  */
  177. var MillisecondsPerWeek = DaysPerWeek * MillisecondsPerDay;
  178.  
  179. /**
  180.  * @constructor
  181.  */
  182. function DateType() {
  183. }
  184.  
  185. /**
  186.  * @constructor
  187.  * @extends DateType
  188.  * @param {!number} year
  189.  * @param {!number} month
  190.  * @param {!number} date
  191.  */
  192. function Day(year, month, date) {
  193.     var dateObject = createUTCDate(year, month, date);
  194.     if (isNaN(dateObject.valueOf()))
  195.         throw "Invalid date";
  196.     /**
  197.      * @type {number}
  198.      * @const
  199.      */
  200.     this.year = dateObject.getUTCFullYear();   
  201.      /**
  202.      * @type {number}
  203.      * @const
  204.      */  
  205.     this.month = dateObject.getUTCMonth();
  206.     /**
  207.      * @type {number}
  208.      * @const
  209.      */
  210.     this.date = dateObject.getUTCDate();
  211. };
  212.  
  213. Day.prototype = Object.create(DateType.prototype);
  214.  
  215. Day.ISOStringRegExp = /^(\d+)-(\d+)-(\d+)/;
  216.  
  217. /**
  218.  * @param {!string} str
  219.  * @return {?Day}
  220.  */
  221. Day.parse = function(str) {
  222.     var match = Day.ISOStringRegExp.exec(str);
  223.     if (!match)
  224.         return null;
  225.     var year = parseInt(match[1], 10);
  226.     var month = parseInt(match[2], 10) - 1;
  227.     var date = parseInt(match[3], 10);
  228.     return new Day(year, month, date);
  229. };
  230.  
  231. /**
  232.  * @param {!number} value
  233.  * @return {!Day}
  234.  */
  235. Day.createFromValue = function(millisecondsSinceEpoch) {
  236.     return Day.createFromDate(new Date(millisecondsSinceEpoch))
  237. };
  238.  
  239. /**
  240.  * @param {!Date} date
  241.  * @return {!Day}
  242.  */
  243. Day.createFromDate = function(date) {
  244.     if (isNaN(date.valueOf()))
  245.         throw "Invalid date";
  246.     return new Day(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
  247. };
  248.  
  249. /**
  250.  * @param {!Day} day
  251.  * @return {!Day}
  252.  */
  253. Day.createFromDay = function(day) {
  254.     return day;
  255. };
  256.  
  257. /**
  258.  * @return {!Day}
  259.  */
  260. Day.createFromToday = function() {
  261.     var now = new Date();
  262.     return new Day(now.getFullYear(), now.getMonth(), now.getDate());
  263. };
  264.  
  265. /**
  266.  * @param {!DateType} other
  267.  * @return {!boolean}
  268.  */
  269. Day.prototype.equals = function(other) {
  270.     return other instanceof Day && this.year === other.year && this.month === other.month && this.date === other.date;
  271. };
  272.  
  273. /**
  274.  * @param {!number=} offset
  275.  * @return {!Day}
  276.  */
  277. Day.prototype.previous = function(offset) {
  278.     if (typeof offset === "undefined")
  279.         offset = 1;
  280.     return new Day(this.year, this.month, this.date - offset);
  281. };
  282.  
  283. /**
  284.  * @param {!number=} offset
  285.  * @return {!Day}
  286.  */
  287. Day.prototype.next = function(offset) {
  288.  if (typeof offset === "undefined")
  289.      offset = 1;
  290.     return new Day(this.year, this.month, this.date + offset);
  291. };
  292.  
  293. /**
  294.  * @return {!Date}
  295.  */
  296. Day.prototype.startDate = function() {
  297.     return createUTCDate(this.year, this.month, this.date);
  298. };
  299.  
  300. /**
  301.  * @return {!Date}
  302.  */
  303. Day.prototype.endDate = function() {
  304.     return createUTCDate(this.year, this.month, this.date + 1);
  305. };
  306.  
  307. /**
  308.  * @return {!Day}
  309.  */
  310. Day.prototype.firstDay = function() {
  311.     return this;
  312. };
  313.  
  314. /**
  315.  * @return {!Day}
  316.  */
  317. Day.prototype.middleDay = function() {
  318.     return this;
  319. };
  320.  
  321. /**
  322.  * @return {!Day}
  323.  */
  324. Day.prototype.lastDay = function() {
  325.     return this;
  326. };
  327.  
  328. /**
  329.  * @return {!number}
  330.  */
  331. Day.prototype.valueOf = function() {
  332.     return createUTCDate(this.year, this.month, this.date).getTime();
  333. };
  334.  
  335. /**
  336.  * @return {!WeekDay}
  337.  */
  338. Day.prototype.weekDay = function() {
  339.     return createUTCDate(this.year, this.month, this.date).getUTCDay();
  340. };
  341.  
  342. /**
  343.  * @return {!string}
  344.  */
  345. Day.prototype.toString = function() {
  346.     var yearString = String(this.year);
  347.     if (yearString.length < 4)
  348.         yearString = ("000" + yearString).substr(-4, 4);
  349.     return yearString + "-" + ("0" + (this.month + 1)).substr(-2, 2) + "-" + ("0" + this.date).substr(-2, 2);
  350. };
  351.  
  352. /**
  353.  * @return {!string}
  354.  */
  355. Day.prototype.format = function() {
  356.     if (!Day.formatter) {
  357.         Day.formatter = new Intl.DateTimeFormat(getLocale(), {
  358.             weekday: "long", year: "numeric", month: "long", day: "numeric", timeZone: "UTC"
  359.         });
  360.     }
  361.     return Day.formatter.format(this.startDate());
  362. };
  363.  
  364. // See WebCore/platform/DateComponents.h.
  365. Day.Minimum = Day.createFromValue(-62135596800000.0);
  366. Day.Maximum = Day.createFromValue(8640000000000000.0);
  367.  
  368. // See WebCore/html/DayInputType.cpp.
  369. Day.DefaultStep = 86400000;
  370. Day.DefaultStepBase = 0;
  371.  
  372. /**
  373.  * @constructor
  374.  * @extends DateType
  375.  * @param {!number} year
  376.  * @param {!number} week
  377.  */
  378. function Week(year, week) { 
  379.     /**
  380.      * @type {number}
  381.      * @const
  382.      */
  383.     this.year = year;
  384.     /**
  385.      * @type {number}
  386.      * @const
  387.      */
  388.     this.week = week;
  389.     // Number of years per year is either 52 or 53.
  390.     if (this.week < 1 || (this.week > 52 && this.week > Week.numberOfWeeksInYear(this.year))) {
  391.         var normalizedWeek = Week.createFromDay(this.firstDay());
  392.         this.year = normalizedWeek.year;
  393.         this.week = normalizedWeek.week;
  394.     }
  395. }
  396.  
  397. Week.ISOStringRegExp = /^(\d+)-[wW](\d+)$/;
  398.  
  399. // See WebCore/platform/DateComponents.h.
  400. Week.Minimum = new Week(1, 1);
  401. Week.Maximum = new Week(275760, 37);
  402.  
  403. // See WebCore/html/WeekInputType.cpp.
  404. Week.DefaultStep = 604800000;
  405. Week.DefaultStepBase = -259200000;
  406.  
  407. Week.EpochWeekDay = createUTCDate(1970, 0, 0).getUTCDay();
  408.  
  409. /**
  410.  * @param {!string} str
  411.  * @return {?Week}
  412.  */
  413. Week.parse = function(str) {
  414.     var match = Week.ISOStringRegExp.exec(str);
  415.     if (!match)
  416.         return null;
  417.     var year = parseInt(match[1], 10);
  418.     var week = parseInt(match[2], 10);
  419.     return new Week(year, week);
  420. };
  421.  
  422. /**
  423.  * @param {!number} millisecondsSinceEpoch
  424.  * @return {!Week}
  425.  */
  426. Week.createFromValue = function(millisecondsSinceEpoch) {
  427.     return Week.createFromDate(new Date(millisecondsSinceEpoch))
  428. };
  429.  
  430. /**
  431.  * @param {!Date} date
  432.  * @return {!Week}
  433.  */
  434. Week.createFromDate = function(date) {
  435.     if (isNaN(date.valueOf()))
  436.         throw "Invalid date";
  437.     var year = date.getUTCFullYear();
  438.     if (year <= Week.Maximum.year && Week.weekOneStartDateForYear(year + 1).getTime() <= date.getTime())
  439.         year++;
  440.     else if (year > 1 && Week.weekOneStartDateForYear(year).getTime() > date.getTime())
  441.         year--;
  442.     var week = 1 + Week._numberOfWeeksSinceDate(Week.weekOneStartDateForYear(year), date);
  443.     return new Week(year, week);
  444. };
  445.  
  446. /**
  447.  * @param {!Day} day
  448.  * @return {!Week}
  449.  */
  450. Week.createFromDay = function(day) {
  451.     var year = day.year;
  452.     if (year <= Week.Maximum.year && Week.weekOneStartDayForYear(year + 1) <= day)
  453.         year++;
  454.     else if (year > 1 && Week.weekOneStartDayForYear(year) > day)
  455.         year--;
  456.     var week = Math.floor(1 + (day.valueOf() - Week.weekOneStartDayForYear(year).valueOf()) / MillisecondsPerWeek);
  457.     return new Week(year, week);
  458. };
  459.  
  460. /**
  461.  * @return {!Week}
  462.  */
  463. Week.createFromToday = function() {
  464.     var now = new Date();
  465.     return Week.createFromDate(createUTCDate(now.getFullYear(), now.getMonth(), now.getDate()));
  466. };
  467.  
  468. /**
  469.  * @param {!number} year
  470.  * @return {!Date}
  471.  */
  472. Week.weekOneStartDateForYear = function(year) {
  473.     if (year < 1)
  474.         return createUTCDate(1, 0, 1);
  475.     // The week containing January 4th is week one.
  476.     var yearStartDay = createUTCDate(year, 0, 4).getUTCDay();
  477.     return createUTCDate(year, 0, 4 - (yearStartDay + 6) % DaysPerWeek);
  478. };
  479.  
  480. /**
  481.  * @param {!number} year
  482.  * @return {!Day}
  483.  */
  484. Week.weekOneStartDayForYear = function(year) {
  485.     if (year < 1)
  486.         return Day.Minimum;
  487.     // The week containing January 4th is week one.
  488.     var yearStartDay = createUTCDate(year, 0, 4).getUTCDay();
  489.     return new Day(year, 0, 4 - (yearStartDay + 6) % DaysPerWeek);
  490. };
  491.  
  492. /**
  493.  * @param {!number} year
  494.  * @return {!number}
  495.  */
  496. Week.numberOfWeeksInYear = function(year) {
  497.     if (year < 1 || year > Week.Maximum.year)
  498.         return 0;
  499.     else if (year === Week.Maximum.year)
  500.         return Week.Maximum.week;
  501.     return Week._numberOfWeeksSinceDate(Week.weekOneStartDateForYear(year), Week.weekOneStartDateForYear(year + 1));
  502. };
  503.  
  504. /**
  505.  * @param {!Date} baseDate
  506.  * @param {!Date} date
  507.  * @return {!number}
  508.  */
  509. Week._numberOfWeeksSinceDate = function(baseDate, date) {
  510.     return Math.floor((date.getTime() - baseDate.getTime()) / MillisecondsPerWeek);
  511. };
  512.  
  513. /**
  514.  * @param {!DateType} other
  515.  * @return {!boolean}
  516.  */
  517. Week.prototype.equals = function(other) {
  518.     return other instanceof Week && this.year === other.year && this.week === other.week;
  519. };
  520.  
  521. /**
  522.  * @param {!number=} offset
  523.  * @return {!Week}
  524.  */
  525. Week.prototype.previous = function(offset) {
  526.     if (typeof offset === "undefined")
  527.         offset = 1;
  528.     return new Week(this.year, this.week - offset);
  529. };
  530.  
  531. /**
  532.  * @param {!number=} offset
  533.  * @return {!Week}
  534.  */
  535. Week.prototype.next = function(offset) {
  536.     if (typeof offset === "undefined")
  537.         offset = 1;
  538.     return new Week(this.year, this.week + offset);
  539. };
  540.  
  541. /**
  542.  * @return {!Date}
  543.  */
  544. Week.prototype.startDate = function() {
  545.     var weekStartDate = Week.weekOneStartDateForYear(this.year);
  546.     weekStartDate.setUTCDate(weekStartDate.getUTCDate() + (this.week - 1) * 7);
  547.     return weekStartDate;
  548. };
  549.  
  550. /**
  551.  * @return {!Date}
  552.  */
  553. Week.prototype.endDate = function() {
  554.     if (this.equals(Week.Maximum))
  555.         return Day.Maximum.startDate();
  556.     return this.next().startDate();
  557. };
  558.  
  559. /**
  560.  * @return {!Day}
  561.  */
  562. Week.prototype.firstDay = function() {
  563.     var weekOneStartDay = Week.weekOneStartDayForYear(this.year);
  564.     return weekOneStartDay.next((this.week - 1) * DaysPerWeek);
  565. };
  566.  
  567. /**
  568.  * @return {!Day}
  569.  */
  570. Week.prototype.middleDay = function() {
  571.     return this.firstDay().next(3);
  572. };
  573.  
  574. /**
  575.  * @return {!Day}
  576.  */
  577. Week.prototype.lastDay = function() {
  578.     if (this.equals(Week.Maximum))
  579.         return Day.Maximum;
  580.     return this.next().firstDay().previous();
  581. };
  582.  
  583. /**
  584.  * @return {!number}
  585.  */
  586. Week.prototype.valueOf = function() {
  587.     return this.firstDay().valueOf() - createUTCDate(1970, 0, 1).getTime();
  588. };
  589.  
  590. /**
  591.  * @return {!string}
  592.  */
  593. Week.prototype.toString = function() {
  594.     var yearString = String(this.year);
  595.     if (yearString.length < 4)
  596.         yearString = ("000" + yearString).substr(-4, 4);
  597.     return yearString + "-W" + ("0" + this.week).substr(-2, 2);
  598. };
  599.  
  600. /**
  601.  * @constructor
  602.  * @extends DateType
  603.  * @param {!number} year
  604.  * @param {!number} month
  605.  */
  606. function Month(year, month) { 
  607.     /**
  608.      * @type {number}
  609.      * @const
  610.      */
  611.     this.year = year + Math.floor(month / MonthsPerYear);
  612.     /**
  613.      * @type {number}
  614.      * @const
  615.      */
  616.     this.month = month % MonthsPerYear < 0 ? month % MonthsPerYear + MonthsPerYear : month % MonthsPerYear;
  617. };
  618.  
  619. Month.ISOStringRegExp = /^(\d+)-(\d+)$/;
  620.  
  621. // See WebCore/platform/DateComponents.h.
  622. Month.Minimum = new Month(1, 0);
  623. Month.Maximum = new Month(275760, 8);
  624.  
  625. // See WebCore/html/MonthInputType.cpp.
  626. Month.DefaultStep = 1;
  627. Month.DefaultStepBase = 0;
  628.  
  629. /**
  630.  * @param {!string} str
  631.  * @return {?Month}
  632.  */
  633. Month.parse = function(str) {
  634.     var match = Month.ISOStringRegExp.exec(str);
  635.     if (!match)
  636.         return null;
  637.     var year = parseInt(match[1], 10);
  638.     var month = parseInt(match[2], 10) - 1;
  639.     return new Month(year, month);
  640. };
  641.  
  642. /**
  643.  * @param {!number} value
  644.  * @return {!Month}
  645.  */
  646. Month.createFromValue = function(monthsSinceEpoch) {
  647.     return new Month(1970, monthsSinceEpoch)
  648. };
  649.  
  650. /**
  651.  * @param {!Date} date
  652.  * @return {!Month}
  653.  */
  654. Month.createFromDate = function(date) {
  655.     if (isNaN(date.valueOf()))
  656.         throw "Invalid date";
  657.     return new Month(date.getUTCFullYear(), date.getUTCMonth());
  658. };
  659.  
  660. /**
  661.  * @param {!Day} day
  662.  * @return {!Month}
  663.  */
  664. Month.createFromDay = function(day) {
  665.     return new Month(day.year, day.month);
  666. };
  667.  
  668. /**
  669.  * @return {!Month}
  670.  */
  671. Month.createFromToday = function() {
  672.     var now = new Date();
  673.     return new Month(now.getFullYear(), now.getMonth());
  674. };
  675.  
  676. /**
  677.  * @return {!boolean}
  678.  */
  679. Month.prototype.containsDay = function(day) {
  680.     return this.year === day.year && this.month === day.month;
  681. };
  682.  
  683. /**
  684.  * @param {!Month} other
  685.  * @return {!boolean}
  686.  */
  687. Month.prototype.equals = function(other) {
  688.     return other instanceof Month && this.year === other.year && this.month === other.month;
  689. };
  690.  
  691. /**
  692.  * @param {!number=} offset
  693.  * @return {!Month}
  694.  */
  695. Month.prototype.previous = function(offset) {
  696.     if (typeof offset === "undefined")
  697.         offset = 1;
  698.     return new Month(this.year, this.month - offset);
  699. };
  700.  
  701. /**
  702.  * @param {!number=} offset
  703.  * @return {!Month}
  704.  */
  705. Month.prototype.next = function(offset) {
  706.     if (typeof offset === "undefined")
  707.         offset = 1;
  708.     return new Month(this.year, this.month + offset);
  709. };
  710.  
  711. /**
  712.  * @return {!Date}
  713.  */
  714. Month.prototype.startDate = function() {
  715.     return createUTCDate(this.year, this.month, 1);
  716. };
  717.  
  718. /**
  719.  * @return {!Date}
  720.  */
  721. Month.prototype.endDate = function() {
  722.     if (this.equals(Month.Maximum))
  723.         return Day.Maximum.startDate();
  724.     return this.next().startDate();
  725. };
  726.  
  727. /**
  728.  * @return {!Day}
  729.  */
  730. Month.prototype.firstDay = function() {
  731.     return new Day(this.year, this.month, 1);
  732. };
  733.  
  734. /**
  735.  * @return {!Day}
  736.  */
  737. Month.prototype.middleDay = function() {
  738.     return new Day(this.year, this.month, this.month === 2 ? 14 : 15);
  739. };
  740.  
  741. /**
  742.  * @return {!Day}
  743.  */
  744. Month.prototype.lastDay = function() {
  745.     if (this.equals(Month.Maximum))
  746.         return Day.Maximum;
  747.     return this.next().firstDay().previous();
  748. };
  749.  
  750. /**
  751.  * @return {!number}
  752.  */
  753. Month.prototype.valueOf = function() {
  754.     return (this.year - 1970) * MonthsPerYear + this.month;
  755. };
  756.  
  757. /**
  758.  * @return {!string}
  759.  */
  760. Month.prototype.toString = function() {
  761.     var yearString = String(this.year);
  762.     if (yearString.length < 4)
  763.         yearString = ("000" + yearString).substr(-4, 4);
  764.     return yearString + "-" + ("0" + (this.month + 1)).substr(-2, 2);
  765. };
  766.  
  767. /**
  768.  * @return {!string}
  769.  */
  770. Month.prototype.toLocaleString = function() {
  771.     if (global.params.locale === "ja")
  772.         return "" + this.year + "\u5e74" + formatJapaneseImperialEra(this.year, this.month) + " " + (this.month + 1) + "\u6708";
  773.     return window.pagePopupController.formatMonth(this.year, this.month);
  774. };
  775.  
  776. /**
  777.  * @return {!string}
  778.  */
  779. Month.prototype.toShortLocaleString = function() {
  780.     return window.pagePopupController.formatShortMonth(this.year, this.month);
  781. };
  782.  
  783. // ----------------------------------------------------------------
  784. // Initialization
  785.  
  786. /**
  787.  * @param {Event} event
  788.  */
  789. function handleMessage(event) {
  790.     if (global.argumentsReceived)
  791.         return;
  792.     global.argumentsReceived = true;
  793.     initialize(JSON.parse(event.data));
  794. }
  795.  
  796. /**
  797.  * @param {!Object} params
  798.  */
  799. function setGlobalParams(params) {
  800.     var name;
  801.     for (name in global.params) {
  802.         if (typeof params[name] === "undefined")
  803.             console.warn("Missing argument: " + name);
  804.     }
  805.     for (name in params) {
  806.         global.params[name] = params[name];
  807.     }
  808. };
  809.  
  810. /**
  811.  * @param {!Object} args
  812.  */
  813. function initialize(args) { 
  814.     setGlobalParams(args);
  815.     if (global.params.suggestionValues && global.params.suggestionValues.length)
  816.         openSuggestionPicker();
  817.     else
  818.         openCalendarPicker();
  819. }
  820.  
  821. function closePicker() {
  822.     if (global.picker)
  823.         global.picker.cleanup();
  824.     var main = $("main");
  825.     main.innerHTML = "";
  826.     main.className = "";
  827. };
  828.  
  829. function openSuggestionPicker() {
  830.     closePicker();
  831.     global.picker = new SuggestionPicker($("main"), global.params);
  832. };
  833.  
  834. function openCalendarPicker() {
  835.     closePicker();
  836.     global.picker = new CalendarPicker(global.params.mode, global.params);
  837.     global.picker.attachTo($("main"));
  838. };
  839.  
  840. /**
  841.  * @constructor
  842.  */
  843. function EventEmitter() {
  844. };
  845.  
  846. /**
  847.  * @param {!string} type
  848.  * @param {!function({...*})} callback
  849.  */
  850. EventEmitter.prototype.on = function(type, callback) {
  851.     console.assert(callback instanceof Function);
  852.     if (!this._callbacks)
  853.         this._callbacks = {};
  854.     if (!this._callbacks[type])
  855.         this._callbacks[type] = [];
  856.     this._callbacks[type].push(callback);
  857. };
  858.  
  859. EventEmitter.prototype.hasListener = function(type) {
  860.     if (!this._callbacks)
  861.         return false;
  862.     var callbacksForType = this._callbacks[type];
  863.     if (!callbacksForType)
  864.         return false;
  865.     return callbacksForType.length > 0;
  866. };
  867.  
  868. /**
  869.  * @param {!string} type
  870.  * @param {!function(Object)} callback
  871.  */
  872. EventEmitter.prototype.removeListener = function(type, callback) {
  873.     if (!this._callbacks)
  874.         return;
  875.     var callbacksForType = this._callbacks[type];
  876.     if (!callbacksForType)
  877.         return;
  878.     callbacksForType.splice(callbacksForType.indexOf(callback), 1);
  879.     if (callbacksForType.length === 0)
  880.         delete this._callbacks[type];
  881. };
  882.  
  883. /**
  884.  * @param {!string} type
  885.  * @param {...*} var_args
  886.  */
  887. EventEmitter.prototype.dispatchEvent = function(type) {
  888.     if (!this._callbacks)
  889.         return;
  890.     var callbacksForType = this._callbacks[type];
  891.     if (!callbacksForType)
  892.         return;
  893.     callbacksForType = callbacksForType.slice(0);
  894.     for (var i = 0; i < callbacksForType.length; ++i) {
  895.         callbacksForType[i].apply(this, Array.prototype.slice.call(arguments, 1));
  896.     }
  897. };
  898.  
  899. // Parameter t should be a number between 0 and 1.
  900. var AnimationTimingFunction = {
  901.     Linear: function(t){
  902.         return t;
  903.     },
  904.     EaseInOut: function(t){
  905.         t *= 2;
  906.         if (t < 1)
  907.             return Math.pow(t, 3) / 2;
  908.         t -= 2;
  909.         return Math.pow(t, 3) / 2 + 1;
  910.     }
  911. };
  912.  
  913. /**
  914.  * @constructor
  915.  * @extends EventEmitter
  916.  */
  917. function AnimationManager() {
  918.     EventEmitter.call(this);
  919.  
  920.     this._isRunning = false;
  921.     this._runningAnimatorCount = 0;
  922.     this._runningAnimators = {};
  923.     this._animationFrameCallbackBound = this._animationFrameCallback.bind(this);
  924. }
  925.  
  926. AnimationManager.prototype = Object.create(EventEmitter.prototype);
  927.  
  928. AnimationManager.EventTypeAnimationFrameWillFinish = "animationFrameWillFinish";
  929.  
  930. AnimationManager.prototype._startAnimation = function() {
  931.     if (this._isRunning)
  932.         return;
  933.     this._isRunning = true;
  934.     window.requestAnimationFrame(this._animationFrameCallbackBound);
  935. };
  936.  
  937. AnimationManager.prototype._stopAnimation = function() {
  938.     if (!this._isRunning)
  939.         return;
  940.     this._isRunning = false;
  941. };
  942.  
  943. /**
  944.  * @param {!Animator} animator
  945.  */
  946. AnimationManager.prototype.add = function(animator) {
  947.     if (this._runningAnimators[animator.id])
  948.         return;
  949.     this._runningAnimators[animator.id] = animator;
  950.     this._runningAnimatorCount++;
  951.     if (this._needsTimer())
  952.         this._startAnimation();
  953. };
  954.  
  955. /**
  956.  * @param {!Animator} animator
  957.  */
  958. AnimationManager.prototype.remove = function(animator) {
  959.     if (!this._runningAnimators[animator.id])
  960.         return;
  961.     delete this._runningAnimators[animator.id];
  962.     this._runningAnimatorCount--;
  963.     if (!this._needsTimer())
  964.         this._stopAnimation();
  965. };
  966.  
  967. AnimationManager.prototype._animationFrameCallback = function(now) {
  968.     if (this._runningAnimatorCount > 0) {
  969.         for (var id in this._runningAnimators) {
  970.             this._runningAnimators[id].onAnimationFrame(now);
  971.         }
  972.     }
  973.     this.dispatchEvent(AnimationManager.EventTypeAnimationFrameWillFinish);
  974.     if (this._isRunning)
  975.         window.requestAnimationFrame(this._animationFrameCallbackBound);
  976. };
  977.  
  978. /**
  979.  * @return {!boolean}
  980.  */
  981. AnimationManager.prototype._needsTimer = function() {
  982.     return this._runningAnimatorCount > 0 || this.hasListener(AnimationManager.EventTypeAnimationFrameWillFinish);
  983. };
  984.  
  985. /**
  986.  * @param {!string} type
  987.  * @param {!Function} callback
  988.  * @override
  989.  */
  990. AnimationManager.prototype.on = function(type, callback) {
  991.     EventEmitter.prototype.on.call(this, type, callback);
  992.     if (this._needsTimer())
  993.         this._startAnimation();
  994. };
  995.  
  996. /**
  997.  * @param {!string} type
  998.  * @param {!Function} callback
  999.  * @override
  1000.  */
  1001. AnimationManager.prototype.removeListener = function(type, callback) {
  1002.     EventEmitter.prototype.removeListener.call(this, type, callback);
  1003.     if (!this._needsTimer())
  1004.         this._stopAnimation();
  1005. };
  1006.  
  1007. AnimationManager.shared = new AnimationManager();
  1008.  
  1009. /**
  1010.  * @constructor
  1011.  * @extends EventEmitter
  1012.  */
  1013. function Animator() {
  1014.     EventEmitter.call(this);
  1015.  
  1016.     /**
  1017.      * @type {!number}
  1018.      * @const
  1019.      */
  1020.     this.id = Animator._lastId++;
  1021.     /**
  1022.      * @type {!number}
  1023.      */
  1024.     this.duration = 100;
  1025.     /**
  1026.      * @type {?function}
  1027.      */
  1028.     this.step = null;
  1029.     /**
  1030.      * @type {!boolean}
  1031.      * @protected
  1032.      */
  1033.     this._isRunning = false;
  1034.     /**
  1035.      * @type {!number}
  1036.      */
  1037.     this.currentValue = 0;
  1038.     /**
  1039.      * @type {!number}
  1040.      * @protected
  1041.      */
  1042.     this._lastStepTime = 0;
  1043. }
  1044.  
  1045. Animator.prototype = Object.create(EventEmitter.prototype);
  1046.  
  1047. Animator._lastId = 0;
  1048.  
  1049. Animator.EventTypeDidAnimationStop = "didAnimationStop";
  1050.  
  1051. /**
  1052.  * @return {!boolean}
  1053.  */
  1054. Animator.prototype.isRunning = function() {
  1055.     return this._isRunning;
  1056. };
  1057.  
  1058. Animator.prototype.start = function() {
  1059.     this._lastStepTime = performance.now();
  1060.     this._isRunning = true;
  1061.     AnimationManager.shared.add(this);
  1062. };
  1063.  
  1064. Animator.prototype.stop = function() {
  1065.     if (!this._isRunning)
  1066.         return;
  1067.     this._isRunning = false;
  1068.     AnimationManager.shared.remove(this);
  1069.     this.dispatchEvent(Animator.EventTypeDidAnimationStop, this);
  1070. };
  1071.  
  1072. /**
  1073.  * @param {!number} now
  1074.  */
  1075. Animator.prototype.onAnimationFrame = function(now) {
  1076.     this._lastStepTime = now;
  1077.     this.step(this);
  1078. };
  1079.  
  1080. /**
  1081.  * @constructor
  1082.  * @extends Animator
  1083.  */
  1084. function TransitionAnimator() {
  1085.     Animator.call(this);
  1086.     /**
  1087.      * @type {!number}
  1088.      * @protected
  1089.      */
  1090.     this._from = 0;
  1091.     /**
  1092.      * @type {!number}
  1093.      * @protected
  1094.      */
  1095.     this._to = 0;
  1096.     /**
  1097.      * @type {!number}
  1098.      * @protected
  1099.      */
  1100.     this._delta = 0;
  1101.     /**
  1102.      * @type {!number}
  1103.      */
  1104.     this.progress = 0.0;
  1105.     /**
  1106.      * @type {!function}
  1107.      */
  1108.     this.timingFunction = AnimationTimingFunction.Linear;
  1109. }
  1110.  
  1111. TransitionAnimator.prototype = Object.create(Animator.prototype);
  1112.  
  1113. /**
  1114.  * @param {!number} value
  1115.  */
  1116. TransitionAnimator.prototype.setFrom = function(value) {
  1117.     this._from = value;
  1118.     this._delta = this._to - this._from;
  1119. };
  1120.  
  1121. TransitionAnimator.prototype.start = function() {
  1122.     console.assert(isFinite(this.duration));
  1123.     this.progress = 0.0;
  1124.     this.currentValue = this._from;
  1125.     Animator.prototype.start.call(this);
  1126. };
  1127.  
  1128. /**
  1129.  * @param {!number} value
  1130.  */
  1131. TransitionAnimator.prototype.setTo = function(value) {
  1132.     this._to = value;
  1133.     this._delta = this._to - this._from;
  1134. };
  1135.  
  1136. /**
  1137.  * @param {!number} now
  1138.  */
  1139. TransitionAnimator.prototype.onAnimationFrame = function(now) {
  1140.     this.progress += (now - this._lastStepTime) / this.duration;
  1141.     this.progress = Math.min(1.0, this.progress);
  1142.     this._lastStepTime = now;
  1143.     this.currentValue = this.timingFunction(this.progress) * this._delta + this._from;
  1144.     this.step(this);
  1145.     if (this.progress === 1.0) {
  1146.         this.stop();
  1147.         return;
  1148.     }
  1149. };
  1150.  
  1151. /**
  1152.  * @constructor
  1153.  * @extends Animator
  1154.  * @param {!number} initialVelocity
  1155.  * @param {!number} initialValue
  1156.  */
  1157. function FlingGestureAnimator(initialVelocity, initialValue) {
  1158.     Animator.call(this);
  1159.     /**
  1160.      * @type {!number}
  1161.      */
  1162.     this.initialVelocity = initialVelocity;
  1163.     /**
  1164.      * @type {!number}
  1165.      */
  1166.     this.initialValue = initialValue;
  1167.     /**
  1168.      * @type {!number}
  1169.      * @protected
  1170.      */
  1171.     this._elapsedTime = 0;
  1172.     var startVelocity = Math.abs(this.initialVelocity);
  1173.     if (startVelocity > this._velocityAtTime(0))
  1174.         startVelocity = this._velocityAtTime(0);
  1175.     if (startVelocity < 0)
  1176.         startVelocity = 0;
  1177.     /**
  1178.      * @type {!number}
  1179.      * @protected
  1180.      */
  1181.     this._timeOffset = this._timeAtVelocity(startVelocity);
  1182.     /**
  1183.      * @type {!number}
  1184.      * @protected
  1185.      */
  1186.     this._positionOffset = this._valueAtTime(this._timeOffset);
  1187.     /**
  1188.      * @type {!number}
  1189.      */
  1190.     this.duration = this._timeAtVelocity(0);
  1191. }
  1192.  
  1193. FlingGestureAnimator.prototype = Object.create(Animator.prototype);
  1194.  
  1195. // Velocity is subject to exponential decay. These parameters are coefficients
  1196. // that determine the curve.
  1197. FlingGestureAnimator._P0 = -5707.62;
  1198. FlingGestureAnimator._P1 = 0.172;
  1199. FlingGestureAnimator._P2 = 0.0037;
  1200.  
  1201. /**
  1202.  * @param {!number} t
  1203.  */
  1204. FlingGestureAnimator.prototype._valueAtTime = function(t) {
  1205.     return FlingGestureAnimator._P0 * Math.exp(-FlingGestureAnimator._P2 * t) - FlingGestureAnimator._P1 * t - FlingGestureAnimator._P0;
  1206. };
  1207.  
  1208. /**
  1209.  * @param {!number} t
  1210.  */
  1211. FlingGestureAnimator.prototype._velocityAtTime = function(t) {
  1212.     return -FlingGestureAnimator._P0 * FlingGestureAnimator._P2 * Math.exp(-FlingGestureAnimator._P2 * t) - FlingGestureAnimator._P1;
  1213. };
  1214.  
  1215. /**
  1216.  * @param {!number} v
  1217.  */
  1218. FlingGestureAnimator.prototype._timeAtVelocity = function(v) {
  1219.     return -Math.log((v + FlingGestureAnimator._P1) / (-FlingGestureAnimator._P0 * FlingGestureAnimator._P2)) / FlingGestureAnimator._P2;
  1220. };
  1221.  
  1222. FlingGestureAnimator.prototype.start = function() {
  1223.     this._lastStepTime = performance.now();
  1224.     Animator.prototype.start.call(this);
  1225. };
  1226.  
  1227. /**
  1228.  * @param {!number} now
  1229.  */
  1230. FlingGestureAnimator.prototype.onAnimationFrame = function(now) {
  1231.     this._elapsedTime += now - this._lastStepTime;
  1232.     this._lastStepTime = now;
  1233.     if (this._elapsedTime + this._timeOffset >= this.duration) {
  1234.         this.stop();
  1235.         return;
  1236.     }
  1237.     var position = this._valueAtTime(this._elapsedTime + this._timeOffset) - this._positionOffset;
  1238.     if (this.initialVelocity < 0)
  1239.         position = -position;
  1240.     this.currentValue = position + this.initialValue;
  1241.     this.step(this);
  1242. };
  1243.  
  1244. /**
  1245.  * @constructor
  1246.  * @extends EventEmitter
  1247.  * @param {?Element} element
  1248.  * View adds itself as a property on the element so we can access it from Event.target.
  1249.  */
  1250. function View(element) {
  1251.     EventEmitter.call(this);
  1252.     /**
  1253.      * @type {Element}
  1254.      * @const
  1255.      */
  1256.     this.element = element || createElement("div");
  1257.     this.element.$view = this;
  1258.     this.bindCallbackMethods();
  1259. }
  1260.  
  1261. View.prototype = Object.create(EventEmitter.prototype);
  1262.  
  1263. /**
  1264.  * @param {!Element} ancestorElement
  1265.  * @return {?Object}
  1266.  */
  1267. View.prototype.offsetRelativeTo = function(ancestorElement) {
  1268.     var x = 0;
  1269.     var y = 0;
  1270.     var element = this.element;
  1271.     while (element) {
  1272.         x += element.offsetLeft  || 0;
  1273.         y += element.offsetTop || 0;
  1274.         element = element.offsetParent;
  1275.         if (element === ancestorElement)
  1276.             return {x: x, y: y};
  1277.     }
  1278.     return null;
  1279. };
  1280.  
  1281. /**
  1282.  * @param {!View|Node} parent
  1283.  * @param {?View|Node=} before
  1284.  */
  1285. View.prototype.attachTo = function(parent, before) {
  1286.     if (parent instanceof View)
  1287.         return this.attachTo(parent.element, before);
  1288.     if (typeof before === "undefined")
  1289.         before = null;
  1290.     if (before instanceof View)
  1291.         before = before.element;
  1292.     parent.insertBefore(this.element, before);
  1293. };
  1294.  
  1295. View.prototype.bindCallbackMethods = function() {
  1296.     for (var methodName in this) {
  1297.         if (!/^on[A-Z]/.test(methodName))
  1298.             continue;
  1299.         if (this.hasOwnProperty(methodName))
  1300.             continue;
  1301.         var method = this[methodName];
  1302.         if (!(method instanceof Function))
  1303.             continue;
  1304.         this[methodName] = method.bind(this);
  1305.     }
  1306. };
  1307.  
  1308. /**
  1309.  * @constructor
  1310.  * @extends View
  1311.  */
  1312. function ScrollView() {
  1313.     View.call(this, createElement("div", ScrollView.ClassNameScrollView));
  1314.     /**
  1315.      * @type {Element}
  1316.      * @const
  1317.      */
  1318.     this.contentElement = createElement("div", ScrollView.ClassNameScrollViewContent);
  1319.     this.element.appendChild(this.contentElement);
  1320.     /**
  1321.      * @type {number}
  1322.      */
  1323.     this.minimumContentOffset = -Infinity;
  1324.     /**
  1325.      * @type {number}
  1326.      */
  1327.     this.maximumContentOffset = Infinity;
  1328.     /**
  1329.      * @type {number}
  1330.      * @protected
  1331.      */
  1332.     this._contentOffset = 0;
  1333.     /**
  1334.      * @type {number}
  1335.      * @protected
  1336.      */
  1337.     this._width = 0;
  1338.     /**
  1339.      * @type {number}
  1340.      * @protected
  1341.      */
  1342.     this._height = 0;
  1343.     /**
  1344.      * @type {Animator}
  1345.      * @protected
  1346.      */
  1347.     this._scrollAnimator = null;
  1348.     /**
  1349.      * @type {?Object}
  1350.      */
  1351.     this.delegate = null;
  1352.     /**
  1353.      * @type {!number}
  1354.      */
  1355.     this._lastTouchPosition = 0;
  1356.     /**
  1357.      * @type {!number}
  1358.      */
  1359.     this._lastTouchVelocity = 0;
  1360.     /**
  1361.      * @type {!number}
  1362.      */
  1363.     this._lastTouchTimeStamp = 0;
  1364.  
  1365.     this.element.addEventListener("mousewheel", this.onMouseWheel, false);
  1366.     this.element.addEventListener("touchstart", this.onTouchStart, false);
  1367.  
  1368.     /**
  1369.      * The content offset is partitioned so the it can go beyond the CSS limit
  1370.      * of 33554433px.
  1371.      * @type {number}
  1372.      * @protected
  1373.      */
  1374.     this._partitionNumber = 0;
  1375. }
  1376.  
  1377. ScrollView.prototype = Object.create(View.prototype);
  1378.  
  1379. ScrollView.PartitionHeight = 100000;
  1380. ScrollView.ClassNameScrollView = "scroll-view";
  1381. ScrollView.ClassNameScrollViewContent = "scroll-view-content";
  1382.  
  1383. /**
  1384.  * @param {!Event} event
  1385.  */
  1386. ScrollView.prototype.onTouchStart = function(event) {
  1387.     var touch = event.touches[0];
  1388.     this._lastTouchPosition = touch.clientY;
  1389.     this._lastTouchVelocity = 0;
  1390.     this._lastTouchTimeStamp = event.timeStamp;
  1391.     if (this._scrollAnimator)
  1392.         this._scrollAnimator.stop();
  1393.     window.addEventListener("touchmove", this.onWindowTouchMove, false);
  1394.     window.addEventListener("touchend", this.onWindowTouchEnd, false);
  1395. };
  1396.  
  1397. /**
  1398.  * @param {!Event} event
  1399.  */
  1400. ScrollView.prototype.onWindowTouchMove = function(event) {
  1401.     var touch = event.touches[0];
  1402.     var deltaTime = event.timeStamp - this._lastTouchTimeStamp;
  1403.     var deltaY = this._lastTouchPosition - touch.clientY;
  1404.     this.scrollBy(deltaY, false);
  1405.     this._lastTouchVelocity = deltaY / deltaTime;
  1406.     this._lastTouchPosition = touch.clientY;
  1407.     this._lastTouchTimeStamp = event.timeStamp;
  1408.     event.stopPropagation();
  1409.     event.preventDefault();
  1410. };
  1411.  
  1412. /**
  1413.  * @param {!Event} event
  1414.  */
  1415. ScrollView.prototype.onWindowTouchEnd = function(event) {
  1416.     if (Math.abs(this._lastTouchVelocity) > 0.01) {
  1417.         this._scrollAnimator = new FlingGestureAnimator(this._lastTouchVelocity, this._contentOffset);
  1418.         this._scrollAnimator.step = this.onFlingGestureAnimatorStep;
  1419.         this._scrollAnimator.start();
  1420.     }
  1421.     window.removeEventListener("touchmove", this.onWindowTouchMove, false);
  1422.     window.removeEventListener("touchend", this.onWindowTouchEnd, false);
  1423. };
  1424.  
  1425. /**
  1426.  * @param {!Animator} animator
  1427.  */
  1428. ScrollView.prototype.onFlingGestureAnimatorStep = function(animator) {
  1429.     this.scrollTo(animator.currentValue, false);
  1430. };
  1431.  
  1432. /**
  1433.  * @return {!Animator}
  1434.  */
  1435. ScrollView.prototype.scrollAnimator = function() {
  1436.     return this._scrollAnimator;
  1437. };
  1438.  
  1439. /**
  1440.  * @param {!number} width
  1441.  */
  1442. ScrollView.prototype.setWidth = function(width) {
  1443.     console.assert(isFinite(width));
  1444.     if (this._width === width)
  1445.         return;
  1446.     this._width = width;
  1447.     this.element.style.width = this._width + "px";
  1448. };
  1449.  
  1450. /**
  1451.  * @return {!number}
  1452.  */
  1453. ScrollView.prototype.width = function() {
  1454.     return this._width;
  1455. };
  1456.  
  1457. /**
  1458.  * @param {!number} height
  1459.  */
  1460. ScrollView.prototype.setHeight = function(height) {
  1461.     console.assert(isFinite(height));
  1462.     if (this._height === height)
  1463.         return;
  1464.     this._height = height;
  1465.     this.element.style.height = height + "px";
  1466.     if (this.delegate)
  1467.         this.delegate.scrollViewDidChangeHeight(this);
  1468. };
  1469.  
  1470. /**
  1471.  * @return {!number}
  1472.  */
  1473. ScrollView.prototype.height = function() {
  1474.     return this._height;
  1475. };
  1476.  
  1477. /**
  1478.  * @param {!Animator} animator
  1479.  */
  1480. ScrollView.prototype.onScrollAnimatorStep = function(animator) {
  1481.     this.setContentOffset(animator.currentValue);
  1482. };
  1483.  
  1484. /**
  1485.  * @param {!number} offset
  1486.  * @param {?boolean} animate
  1487.  */
  1488. ScrollView.prototype.scrollTo = function(offset, animate) {
  1489.     console.assert(isFinite(offset));
  1490.     if (!animate) {
  1491.         this.setContentOffset(offset);
  1492.         return;
  1493.     }
  1494.     if (this._scrollAnimator)
  1495.         this._scrollAnimator.stop();
  1496.     this._scrollAnimator = new TransitionAnimator();
  1497.     this._scrollAnimator.step = this.onScrollAnimatorStep;
  1498.     this._scrollAnimator.setFrom(this._contentOffset);
  1499.     this._scrollAnimator.setTo(offset);
  1500.     this._scrollAnimator.duration = 300;
  1501.     this._scrollAnimator.start();
  1502. };
  1503.  
  1504. /**
  1505.  * @param {!number} offset
  1506.  * @param {?boolean} animate
  1507.  */
  1508. ScrollView.prototype.scrollBy = function(offset, animate) {
  1509.     this.scrollTo(this._contentOffset + offset, animate);
  1510. };
  1511.  
  1512. /**
  1513.  * @return {!number}
  1514.  */
  1515. ScrollView.prototype.contentOffset = function() {
  1516.     return this._contentOffset;
  1517. };
  1518.  
  1519. /**
  1520.  * @param {?Event} event
  1521.  */
  1522. ScrollView.prototype.onMouseWheel = function(event) {
  1523.     this.setContentOffset(this._contentOffset - event.wheelDelta / 30);
  1524.     event.stopPropagation();
  1525.     event.preventDefault();
  1526. };
  1527.  
  1528.  
  1529. /**
  1530.  * @param {!number} value
  1531.  */
  1532. ScrollView.prototype.setContentOffset = function(value) {
  1533.     console.assert(isFinite(value));
  1534.     value = Math.min(this.maximumContentOffset - this._height, Math.max(this.minimumContentOffset, Math.floor(value)));
  1535.     if (this._contentOffset === value)
  1536.         return;
  1537.     this._contentOffset = value;
  1538.     this._updateScrollContent();
  1539.     if (this.delegate)
  1540.         this.delegate.scrollViewDidChangeContentOffset(this);
  1541. };
  1542.  
  1543. ScrollView.prototype._updateScrollContent = function() {
  1544.     var newPartitionNumber = Math.floor(this._contentOffset / ScrollView.PartitionHeight);
  1545.     var partitionChanged = this._partitionNumber !== newPartitionNumber;
  1546.     this._partitionNumber = newPartitionNumber;
  1547.     this.contentElement.style.webkitTransform = "translate(0, " + (-this.contentPositionForContentOffset(this._contentOffset)) + "px)";
  1548.     if (this.delegate && partitionChanged)
  1549.         this.delegate.scrollViewDidChangePartition(this);
  1550. };
  1551.  
  1552. /**
  1553.  * @param {!View|Node} parent
  1554.  * @param {?View|Node=} before
  1555.  * @override
  1556.  */
  1557. ScrollView.prototype.attachTo = function(parent, before) {
  1558.     View.prototype.attachTo.call(this, parent, before);
  1559.     this._updateScrollContent();
  1560. };
  1561.  
  1562. /**
  1563.  * @param {!number} offset
  1564.  */
  1565. ScrollView.prototype.contentPositionForContentOffset = function(offset) {
  1566.     return offset - this._partitionNumber * ScrollView.PartitionHeight;
  1567. };
  1568.  
  1569. /**
  1570.  * @constructor
  1571.  * @extends View
  1572.  */
  1573. function ListCell() {
  1574.     View.call(this, createElement("div", ListCell.ClassNameListCell));
  1575.     
  1576.     /**
  1577.      * @type {!number}
  1578.      */
  1579.     this.row = NaN;
  1580.     /**
  1581.      * @type {!number}
  1582.      */
  1583.     this._width = 0;
  1584.     /**
  1585.      * @type {!number}
  1586.      */
  1587.     this._position = 0;
  1588. }
  1589.  
  1590. ListCell.prototype = Object.create(View.prototype);
  1591.  
  1592. ListCell.DefaultRecycleBinLimit = 64;
  1593. ListCell.ClassNameListCell = "list-cell";
  1594. ListCell.ClassNameHidden = "hidden";
  1595.  
  1596. /**
  1597.  * @return {!Array} An array to keep thrown away cells.
  1598.  */
  1599. ListCell.prototype._recycleBin = function() {
  1600.     console.assert(false, "NOT REACHED: ListCell.prototype._recycleBin needs to be overridden.");
  1601.     return [];
  1602. };
  1603.  
  1604. ListCell.prototype.throwAway = function() {
  1605.     this.hide();
  1606.     var limit = typeof this.constructor.RecycleBinLimit === "undefined" ? ListCell.DefaultRecycleBinLimit : this.constructor.RecycleBinLimit;
  1607.     var recycleBin = this._recycleBin();
  1608.     if (recycleBin.length < limit)
  1609.         recycleBin.push(this);
  1610. };
  1611.  
  1612. ListCell.prototype.show = function() {
  1613.     this.element.classList.remove(ListCell.ClassNameHidden);
  1614. };
  1615.  
  1616. ListCell.prototype.hide = function() {
  1617.     this.element.classList.add(ListCell.ClassNameHidden);
  1618. };
  1619.  
  1620. /**
  1621.  * @return {!number} Width in pixels.
  1622.  */
  1623. ListCell.prototype.width = function(){
  1624.     return this._width;
  1625. };
  1626.  
  1627. /**
  1628.  * @param {!number} width Width in pixels.
  1629.  */
  1630. ListCell.prototype.setWidth = function(width){
  1631.     if (this._width === width)
  1632.         return;
  1633.     this._width = width;
  1634.     this.element.style.width = this._width + "px";
  1635. };
  1636.  
  1637. /**
  1638.  * @return {!number} Position in pixels.
  1639.  */
  1640. ListCell.prototype.position = function(){
  1641.     return this._position;
  1642. };
  1643.  
  1644. /**
  1645.  * @param {!number} y Position in pixels.
  1646.  */
  1647. ListCell.prototype.setPosition = function(y) {
  1648.     if (this._position === y)
  1649.         return;
  1650.     this._position = y;
  1651.     this.element.style.webkitTransform = "translate(0, " + this._position + "px)";
  1652. };
  1653.  
  1654. /**
  1655.  * @param {!boolean} selected
  1656.  */
  1657. ListCell.prototype.setSelected = function(selected) {
  1658.     if (this._selected === selected)
  1659.         return;
  1660.     this._selected = selected;
  1661.     if (this._selected)
  1662.         this.element.classList.add("selected");
  1663.     else
  1664.         this.element.classList.remove("selected");
  1665. };
  1666.  
  1667. /**
  1668.  * @constructor
  1669.  * @extends View
  1670.  */
  1671. function ListView() {
  1672.     View.call(this, createElement("div", ListView.ClassNameListView));
  1673.     this.element.tabIndex = 0;
  1674.     this.element.setAttribute("role", "grid");
  1675.  
  1676.     /**
  1677.      * @type {!number}
  1678.      * @private
  1679.      */
  1680.     this._width = 0;
  1681.     /**
  1682.      * @type {!Object}
  1683.      * @private
  1684.      */
  1685.     this._cells = {};
  1686.  
  1687.     /**
  1688.      * @type {!number}
  1689.      */
  1690.     this.selectedRow = ListView.NoSelection;
  1691.  
  1692.     /**
  1693.      * @type {!ScrollView}
  1694.      */
  1695.     this.scrollView = new ScrollView();
  1696.     this.scrollView.delegate = this;
  1697.     this.scrollView.minimumContentOffset = 0;
  1698.     this.scrollView.setWidth(0);
  1699.     this.scrollView.setHeight(0);
  1700.     this.scrollView.attachTo(this);
  1701.  
  1702.     this.element.addEventListener("click", this.onClick, false);
  1703.  
  1704.     /**
  1705.      * @type {!boolean}
  1706.      * @private
  1707.      */
  1708.     this._needsUpdateCells = false;
  1709. }
  1710.  
  1711. ListView.prototype = Object.create(View.prototype);
  1712.  
  1713. ListView.NoSelection = -1;
  1714. ListView.ClassNameListView = "list-view";
  1715.  
  1716. ListView.prototype.onAnimationFrameWillFinish = function() {
  1717.     if (this._needsUpdateCells)
  1718.         this.updateCells();
  1719. };
  1720.  
  1721. /**
  1722.  * @param {!boolean} needsUpdateCells
  1723.  */
  1724. ListView.prototype.setNeedsUpdateCells = function(needsUpdateCells) {
  1725.     if (this._needsUpdateCells === needsUpdateCells)
  1726.         return;
  1727.     this._needsUpdateCells = needsUpdateCells;
  1728.     if (this._needsUpdateCells)
  1729.         AnimationManager.shared.on(AnimationManager.EventTypeAnimationFrameWillFinish, this.onAnimationFrameWillFinish);
  1730.     else
  1731.         AnimationManager.shared.removeListener(AnimationManager.EventTypeAnimationFrameWillFinish, this.onAnimationFrameWillFinish);
  1732. };
  1733.  
  1734. /**
  1735.  * @param {!number} row
  1736.  * @return {?ListCell}
  1737.  */
  1738. ListView.prototype.cellAtRow = function(row) {
  1739.     return this._cells[row];
  1740. };
  1741.  
  1742. /**
  1743.  * @param {!number} offset Scroll offset in pixels.
  1744.  * @return {!number}
  1745.  */
  1746. ListView.prototype.rowAtScrollOffset = function(offset) {
  1747.     console.assert(false, "NOT REACHED: ListView.prototype.rowAtScrollOffset needs to be overridden.");
  1748.     return 0;
  1749. };
  1750.  
  1751. /**
  1752.  * @param {!number} row
  1753.  * @return {!number} Scroll offset in pixels.
  1754.  */
  1755. ListView.prototype.scrollOffsetForRow = function(row) {
  1756.     console.assert(false, "NOT REACHED: ListView.prototype.scrollOffsetForRow needs to be overridden.");
  1757.     return 0;
  1758. };
  1759.  
  1760. /**
  1761.  * @param {!number} row
  1762.  * @return {!ListCell}
  1763.  */
  1764. ListView.prototype.addCellIfNecessary = function(row) {
  1765.     var cell = this._cells[row];
  1766.     if (cell)
  1767.         return cell;
  1768.     cell = this.prepareNewCell(row);
  1769.     cell.attachTo(this.scrollView.contentElement);
  1770.     cell.setWidth(this._width);
  1771.     cell.setPosition(this.scrollView.contentPositionForContentOffset(this.scrollOffsetForRow(row)));
  1772.     this._cells[row] = cell;
  1773.     return cell;
  1774. };
  1775.  
  1776. /**
  1777.  * @param {!number} row
  1778.  * @return {!ListCell}
  1779.  */
  1780. ListView.prototype.prepareNewCell = function(row) {
  1781.     console.assert(false, "NOT REACHED: ListView.prototype.prepareNewCell should be overridden.");
  1782.     return new ListCell();
  1783. };
  1784.  
  1785. /**
  1786.  * @param {!ListCell} cell
  1787.  */
  1788. ListView.prototype.throwAwayCell = function(cell) {
  1789.     delete this._cells[cell.row];
  1790.     cell.throwAway();
  1791. };
  1792.  
  1793. /**
  1794.  * @return {!number}
  1795.  */
  1796. ListView.prototype.firstVisibleRow = function() {
  1797.     return this.rowAtScrollOffset(this.scrollView.contentOffset());
  1798. };
  1799.  
  1800. /**
  1801.  * @return {!number}
  1802.  */
  1803. ListView.prototype.lastVisibleRow = function() {
  1804.     return this.rowAtScrollOffset(this.scrollView.contentOffset() + this.scrollView.height() - 1);
  1805. };
  1806.  
  1807. /**
  1808.  * @param {!ScrollView} scrollView
  1809.  */
  1810. ListView.prototype.scrollViewDidChangeContentOffset = function(scrollView) {
  1811.     this.setNeedsUpdateCells(true);
  1812. };
  1813.  
  1814. /**
  1815.  * @param {!ScrollView} scrollView
  1816.  */
  1817. ListView.prototype.scrollViewDidChangeHeight = function(scrollView) {
  1818.     this.setNeedsUpdateCells(true);
  1819. };
  1820.  
  1821. /**
  1822.  * @param {!ScrollView} scrollView
  1823.  */
  1824. ListView.prototype.scrollViewDidChangePartition = function(scrollView) {
  1825.     this.setNeedsUpdateCells(true);
  1826. };
  1827.  
  1828. ListView.prototype.updateCells = function() {
  1829.     var firstVisibleRow = this.firstVisibleRow();
  1830.     var lastVisibleRow = this.lastVisibleRow();
  1831.     console.assert(firstVisibleRow <= lastVisibleRow);
  1832.     for (var c in this._cells) {
  1833.         var cell = this._cells[c];
  1834.         if (cell.row < firstVisibleRow || cell.row > lastVisibleRow)
  1835.             this.throwAwayCell(cell);
  1836.     }
  1837.     for (var i = firstVisibleRow; i <= lastVisibleRow; ++i) {
  1838.         var cell = this._cells[i];
  1839.         if (cell)
  1840.             cell.setPosition(this.scrollView.contentPositionForContentOffset(this.scrollOffsetForRow(cell.row)));
  1841.         else
  1842.             this.addCellIfNecessary(i);
  1843.     }
  1844.     this.setNeedsUpdateCells(false);
  1845. };
  1846.  
  1847. /**
  1848.  * @return {!number} Width in pixels.
  1849.  */
  1850. ListView.prototype.width = function() {
  1851.     return this._width;
  1852. };
  1853.  
  1854. /**
  1855.  * @param {!number} width Width in pixels.
  1856.  */
  1857. ListView.prototype.setWidth = function(width) {
  1858.     if (this._width === width)
  1859.         return;
  1860.     this._width = width;
  1861.     this.scrollView.setWidth(this._width);
  1862.     for (var c in this._cells) {
  1863.         this._cells[c].setWidth(this._width);
  1864.     }
  1865.     this.element.style.width = this._width + "px";
  1866.     this.setNeedsUpdateCells(true);
  1867. };
  1868.  
  1869. /**
  1870.  * @return {!number} Height in pixels.
  1871.  */
  1872. ListView.prototype.height = function() {
  1873.     return this.scrollView.height();
  1874. };
  1875.  
  1876. /**
  1877.  * @param {!number} height Height in pixels.
  1878.  */
  1879. ListView.prototype.setHeight = function(height) {
  1880.     this.scrollView.setHeight(height);
  1881. };
  1882.  
  1883. /**
  1884.  * @param {?Event} event
  1885.  */
  1886. ListView.prototype.onClick = function(event) {
  1887.     var clickedCellElement = enclosingNodeOrSelfWithClass(event.target, ListCell.ClassNameListCell);
  1888.     if (!clickedCellElement)
  1889.         return;
  1890.     var clickedCell = clickedCellElement.$view;
  1891.     if (clickedCell.row !== this.selectedRow)
  1892.         this.select(clickedCell.row);
  1893. };
  1894.  
  1895. /**
  1896.  * @param {!number} row
  1897.  */
  1898. ListView.prototype.select = function(row) {
  1899.     if (this.selectedRow === row)
  1900.         return;
  1901.     this.deselect();
  1902.     if (row === ListView.NoSelection)
  1903.         return;
  1904.     this.selectedRow = row;
  1905.     var selectedCell = this._cells[this.selectedRow];
  1906.     if (selectedCell)
  1907.         selectedCell.setSelected(true);
  1908. };
  1909.  
  1910. ListView.prototype.deselect = function() {
  1911.     if (this.selectedRow === ListView.NoSelection)
  1912.         return;
  1913.     var selectedCell = this._cells[this.selectedRow];
  1914.     if (selectedCell)
  1915.         selectedCell.setSelected(false);
  1916.     this.selectedRow = ListView.NoSelection;
  1917. };
  1918.  
  1919. /**
  1920.  * @param {!number} row
  1921.  * @param {!boolean} animate
  1922.  */
  1923. ListView.prototype.scrollToRow = function(row, animate) {
  1924.     this.scrollView.scrollTo(this.scrollOffsetForRow(row), animate);
  1925. };
  1926.  
  1927. /**
  1928.  * @constructor
  1929.  * @extends View
  1930.  * @param {!ScrollView} scrollView
  1931.  */
  1932. function ScrubbyScrollBar(scrollView) {
  1933.     View.call(this, createElement("div", ScrubbyScrollBar.ClassNameScrubbyScrollBar));
  1934.  
  1935.     /**
  1936.      * @type {!Element}
  1937.      * @const
  1938.      */
  1939.     this.thumb = createElement("div", ScrubbyScrollBar.ClassNameScrubbyScrollThumb);
  1940.     this.element.appendChild(this.thumb);
  1941.  
  1942.     /**
  1943.      * @type {!ScrollView}
  1944.      * @const
  1945.      */
  1946.     this.scrollView = scrollView;
  1947.  
  1948.     /**
  1949.      * @type {!number}
  1950.      * @protected
  1951.      */
  1952.     this._height = 0;
  1953.     /**
  1954.      * @type {!number}
  1955.      * @protected
  1956.      */
  1957.     this._thumbHeight = 0;
  1958.     /**
  1959.      * @type {!number}
  1960.      * @protected
  1961.      */
  1962.     this._thumbPosition = 0;
  1963.  
  1964.     this.setHeight(0);
  1965.     this.setThumbHeight(ScrubbyScrollBar.ThumbHeight);
  1966.  
  1967.     /**
  1968.      * @type {?Animator}
  1969.      * @protected
  1970.      */
  1971.     this._thumbStyleTopAnimator = null;
  1972.  
  1973.     /** 
  1974.      * @type {?number}
  1975.      * @protected
  1976.      */
  1977.     this._timer = null;
  1978.     
  1979.     this.element.addEventListener("mousedown", this.onMouseDown, false);
  1980.     this.element.addEventListener("touchstart", this.onTouchStart, false);
  1981. }
  1982.  
  1983. ScrubbyScrollBar.prototype = Object.create(View.prototype);
  1984.  
  1985. ScrubbyScrollBar.ScrollInterval = 16;
  1986. ScrubbyScrollBar.ThumbMargin = 2;
  1987. ScrubbyScrollBar.ThumbHeight = 30;
  1988. ScrubbyScrollBar.ClassNameScrubbyScrollBar = "scrubby-scroll-bar";
  1989. ScrubbyScrollBar.ClassNameScrubbyScrollThumb = "scrubby-scroll-thumb";
  1990.  
  1991. /**
  1992.  * @param {?Event} event
  1993.  */
  1994. ScrubbyScrollBar.prototype.onTouchStart = function(event) {
  1995.     var touch = event.touches[0];
  1996.     this._setThumbPositionFromEventPosition(touch.clientY);
  1997.     if (this._thumbStyleTopAnimator)
  1998.         this._thumbStyleTopAnimator.stop();
  1999.     this._timer = setInterval(this.onScrollTimer, ScrubbyScrollBar.ScrollInterval);
  2000.     window.addEventListener("touchmove", this.onWindowTouchMove, false);
  2001.     window.addEventListener("touchend", this.onWindowTouchEnd, false);
  2002.     event.stopPropagation();
  2003.     event.preventDefault();
  2004. };
  2005.  
  2006. /**
  2007.  * @param {?Event} event
  2008.  */
  2009. ScrubbyScrollBar.prototype.onWindowTouchMove = function(event) {
  2010.     var touch = event.touches[0];
  2011.     this._setThumbPositionFromEventPosition(touch.clientY);
  2012.     event.stopPropagation();
  2013.     event.preventDefault();
  2014. };
  2015.  
  2016. /**
  2017.  * @param {?Event} event
  2018.  */
  2019. ScrubbyScrollBar.prototype.onWindowTouchEnd = function(event) {
  2020.     this._thumbStyleTopAnimator = new TransitionAnimator();
  2021.     this._thumbStyleTopAnimator.step = this.onThumbStyleTopAnimationStep;
  2022.     this._thumbStyleTopAnimator.setFrom(this.thumb.offsetTop);
  2023.     this._thumbStyleTopAnimator.setTo((this._height - this._thumbHeight) / 2);
  2024.     this._thumbStyleTopAnimator.timingFunction = AnimationTimingFunction.EaseInOut;
  2025.     this._thumbStyleTopAnimator.duration = 100;
  2026.     this._thumbStyleTopAnimator.start();
  2027.  
  2028.     window.removeEventListener("touchmove", this.onWindowTouchMove, false);
  2029.     window.removeEventListener("touchend", this.onWindowTouchEnd, false);
  2030.     clearInterval(this._timer);
  2031. };
  2032.  
  2033. /**
  2034.  * @return {!number} Height of the view in pixels.
  2035.  */
  2036. ScrubbyScrollBar.prototype.height = function() {
  2037.     return this._height;
  2038. };
  2039.  
  2040. /**
  2041.  * @param {!number} height Height of the view in pixels.
  2042.  */
  2043. ScrubbyScrollBar.prototype.setHeight = function(height) {
  2044.     if (this._height === height)
  2045.         return;
  2046.     this._height = height;
  2047.     this.element.style.height = this._height + "px";
  2048.     this.thumb.style.top = ((this._height - this._thumbHeight) / 2) + "px";
  2049.     this._thumbPosition = 0;
  2050. };
  2051.  
  2052. /**
  2053.  * @param {!number} height Height of the scroll bar thumb in pixels.
  2054.  */
  2055. ScrubbyScrollBar.prototype.setThumbHeight = function(height) {
  2056.     if (this._thumbHeight === height)
  2057.         return;
  2058.     this._thumbHeight = height;
  2059.     this.thumb.style.height = this._thumbHeight + "px";
  2060.     this.thumb.style.top = ((this._height - this._thumbHeight) / 2) + "px";
  2061.     this._thumbPosition = 0;
  2062. };
  2063.  
  2064. /**
  2065.  * @param {number} position
  2066.  */
  2067. ScrubbyScrollBar.prototype._setThumbPositionFromEventPosition = function(position) {
  2068.     var thumbMin = ScrubbyScrollBar.ThumbMargin;
  2069.     var thumbMax = this._height - this._thumbHeight - ScrubbyScrollBar.ThumbMargin * 2;
  2070.     var y = position - this.element.getBoundingClientRect().top - this.element.clientTop + this.element.scrollTop;
  2071.     var thumbPosition = y - this._thumbHeight / 2;
  2072.     thumbPosition = Math.max(thumbPosition, thumbMin);
  2073.     thumbPosition = Math.min(thumbPosition, thumbMax);
  2074.     this.thumb.style.top = thumbPosition + "px";
  2075.     this._thumbPosition = 1.0 - (thumbPosition - thumbMin) / (thumbMax - thumbMin) * 2;
  2076. };
  2077.  
  2078. /**
  2079.  * @param {?Event} event
  2080.  */
  2081. ScrubbyScrollBar.prototype.onMouseDown = function(event) {
  2082.     this._setThumbPositionFromEventPosition(event.clientY);
  2083.  
  2084.     window.addEventListener("mousemove", this.onWindowMouseMove, false);
  2085.     window.addEventListener("mouseup", this.onWindowMouseUp, false);
  2086.     if (this._thumbStyleTopAnimator)
  2087.         this._thumbStyleTopAnimator.stop();
  2088.     this._timer = setInterval(this.onScrollTimer, ScrubbyScrollBar.ScrollInterval);
  2089.     event.stopPropagation();
  2090.     event.preventDefault();
  2091. };
  2092.  
  2093. /**
  2094.  * @param {?Event} event
  2095.  */
  2096. ScrubbyScrollBar.prototype.onWindowMouseMove = function(event) {
  2097.     this._setThumbPositionFromEventPosition(event.clientY);
  2098. };
  2099.  
  2100. /**
  2101.  * @param {?Event} event
  2102.  */
  2103. ScrubbyScrollBar.prototype.onWindowMouseUp = function(event) {
  2104.     this._thumbStyleTopAnimator = new TransitionAnimator();
  2105.     this._thumbStyleTopAnimator.step = this.onThumbStyleTopAnimationStep;
  2106.     this._thumbStyleTopAnimator.setFrom(this.thumb.offsetTop);
  2107.     this._thumbStyleTopAnimator.setTo((this._height - this._thumbHeight) / 2);
  2108.     this._thumbStyleTopAnimator.timingFunction = AnimationTimingFunction.EaseInOut;
  2109.     this._thumbStyleTopAnimator.duration = 100;
  2110.     this._thumbStyleTopAnimator.start();
  2111.     
  2112.     window.removeEventListener("mousemove", this.onWindowMouseMove, false);
  2113.     window.removeEventListener("mouseup", this.onWindowMouseUp, false);
  2114.     clearInterval(this._timer);
  2115. };
  2116.  
  2117. /**
  2118.  * @param {!Animator} animator
  2119.  */
  2120. ScrubbyScrollBar.prototype.onThumbStyleTopAnimationStep = function(animator) {
  2121.     this.thumb.style.top = animator.currentValue + "px";
  2122. };
  2123.  
  2124. ScrubbyScrollBar.prototype.onScrollTimer = function() {
  2125.     var scrollAmount = Math.pow(this._thumbPosition, 2) * 10;
  2126.     if (this._thumbPosition > 0)
  2127.         scrollAmount = -scrollAmount;
  2128.     this.scrollView.scrollBy(scrollAmount, false);
  2129. };
  2130.  
  2131. /**
  2132.  * @constructor
  2133.  * @extends ListCell
  2134.  * @param {!Array} shortMonthLabels
  2135.  */
  2136. function YearListCell(shortMonthLabels) {
  2137.     ListCell.call(this);
  2138.     this.element.classList.add(YearListCell.ClassNameYearListCell);
  2139.     this.element.style.height = YearListCell.Height + "px";
  2140.  
  2141.     /**
  2142.      * @type {!Element}
  2143.      * @const
  2144.      */
  2145.     this.label = createElement("div", YearListCell.ClassNameLabel, "----");
  2146.     this.element.appendChild(this.label);
  2147.     this.label.style.height = (YearListCell.Height - YearListCell.BorderBottomWidth) + "px";
  2148.     this.label.style.lineHeight = (YearListCell.Height - YearListCell.BorderBottomWidth) + "px";
  2149.  
  2150.     /**
  2151.      * @type {!Array} Array of the 12 month button elements.
  2152.      * @const
  2153.      */
  2154.     this.monthButtons = [];
  2155.     var monthChooserElement = createElement("div", YearListCell.ClassNameMonthChooser);
  2156.     for (var r = 0; r < YearListCell.ButtonRows; ++r) {
  2157.         var buttonsRow = createElement("div", YearListCell.ClassNameMonthButtonsRow);
  2158.         buttonsRow.setAttribute("role", "row");
  2159.         for (var c = 0; c < YearListCell.ButtonColumns; ++c) {
  2160.             var month = c + r * YearListCell.ButtonColumns;
  2161.             var button = createElement("div", YearListCell.ClassNameMonthButton, shortMonthLabels[month]);
  2162.             button.setAttribute("role", "gridcell");
  2163.             button.dataset.month = month;
  2164.             buttonsRow.appendChild(button);
  2165.             this.monthButtons.push(button);
  2166.         }
  2167.         monthChooserElement.appendChild(buttonsRow);
  2168.     }
  2169.     this.element.appendChild(monthChooserElement);
  2170.  
  2171.     /**
  2172.      * @type {!boolean}
  2173.      * @private
  2174.      */
  2175.     this._selected = false;
  2176.     /**
  2177.      * @type {!number}
  2178.      * @private
  2179.      */
  2180.     this._height = 0;
  2181. }
  2182.  
  2183. YearListCell.prototype = Object.create(ListCell.prototype);
  2184.  
  2185. YearListCell.Height = hasInaccuratePointingDevice() ? 31 : 25;
  2186. YearListCell.BorderBottomWidth = 1;
  2187. YearListCell.ButtonRows = 3;
  2188. YearListCell.ButtonColumns = 4;
  2189. YearListCell.SelectedHeight = hasInaccuratePointingDevice() ? 127 : 121;
  2190. YearListCell.ClassNameYearListCell = "year-list-cell";
  2191. YearListCell.ClassNameLabel = "label";
  2192. YearListCell.ClassNameMonthChooser = "month-chooser";
  2193. YearListCell.ClassNameMonthButtonsRow = "month-buttons-row";
  2194. YearListCell.ClassNameMonthButton = "month-button";
  2195. YearListCell.ClassNameHighlighted = "highlighted";
  2196.  
  2197. YearListCell._recycleBin = [];
  2198.  
  2199. /**
  2200.  * @return {!Array}
  2201.  * @override
  2202.  */
  2203. YearListCell.prototype._recycleBin = function() {
  2204.     return YearListCell._recycleBin;
  2205. };
  2206.  
  2207. /**
  2208.  * @param {!number} row
  2209.  */
  2210. YearListCell.prototype.reset = function(row) {
  2211.     this.row = row;
  2212.     this.label.textContent = row + 1;
  2213.     for (var i = 0; i < this.monthButtons.length; ++i) {
  2214.         this.monthButtons[i].classList.remove(YearListCell.ClassNameHighlighted);
  2215.     }
  2216.     this.show();
  2217. };
  2218.  
  2219. /**
  2220.  * @return {!number} The height in pixels.
  2221.  */
  2222. YearListCell.prototype.height = function() {
  2223.     return this._height;
  2224. };
  2225.  
  2226. /**
  2227.  * @param {!number} height Height in pixels.
  2228.  */
  2229. YearListCell.prototype.setHeight = function(height) {
  2230.     if (this._height === height)
  2231.         return;
  2232.     this._height = height;
  2233.     this.element.style.height = this._height + "px";
  2234. };
  2235.  
  2236. /**
  2237.  * @constructor
  2238.  * @extends ListView
  2239.  * @param {!Month} minimumMonth
  2240.  * @param {!Month} maximumMonth
  2241.  */
  2242. function YearListView(minimumMonth, maximumMonth) {
  2243.     ListView.call(this);
  2244.     this.element.classList.add("year-list-view");
  2245.  
  2246.     /**
  2247.      * @type {?Month}
  2248.      */
  2249.     this.highlightedMonth = null;
  2250.     /**
  2251.      * @type {!Month}
  2252.      * @const
  2253.      * @protected
  2254.      */
  2255.     this._minimumMonth = minimumMonth;
  2256.     /**
  2257.      * @type {!Month}
  2258.      * @const
  2259.      * @protected
  2260.      */
  2261.     this._maximumMonth = maximumMonth;
  2262.  
  2263.     this.scrollView.minimumContentOffset = (this._minimumMonth.year - 1) * YearListCell.Height;
  2264.     this.scrollView.maximumContentOffset = (this._maximumMonth.year - 1) * YearListCell.Height + YearListCell.SelectedHeight;
  2265.     
  2266.     /**
  2267.      * @type {!Object}
  2268.      * @const
  2269.      * @protected
  2270.      */
  2271.     this._runningAnimators = {};
  2272.     /**
  2273.      * @type {!Array}
  2274.      * @const
  2275.      * @protected
  2276.      */
  2277.     this._animatingRows = [];
  2278.     /**
  2279.      * @type {!boolean}
  2280.      * @protected
  2281.      */
  2282.     this._ignoreMouseOutUntillNextMouseOver = false;
  2283.     
  2284.     /**
  2285.      * @type {!ScrubbyScrollBar}
  2286.      * @const
  2287.      */
  2288.     this.scrubbyScrollBar = new ScrubbyScrollBar(this.scrollView);
  2289.     this.scrubbyScrollBar.attachTo(this);
  2290.     
  2291.     this.element.addEventListener("mouseover", this.onMouseOver, false);
  2292.     this.element.addEventListener("mouseout", this.onMouseOut, false);
  2293.     this.element.addEventListener("keydown", this.onKeyDown, false);
  2294.     this.element.addEventListener("touchstart", this.onTouchStart, false);
  2295. }
  2296.  
  2297. YearListView.prototype = Object.create(ListView.prototype);
  2298.  
  2299. YearListView.Height = YearListCell.SelectedHeight - 1;
  2300. YearListView.EventTypeYearListViewDidHide = "yearListViewDidHide";
  2301. YearListView.EventTypeYearListViewDidSelectMonth = "yearListViewDidSelectMonth";
  2302.  
  2303. /**
  2304.  * @param {?Event} event
  2305.  */
  2306. YearListView.prototype.onTouchStart = function(event) {
  2307.     var touch = event.touches[0];
  2308.     var monthButtonElement = enclosingNodeOrSelfWithClass(touch.target, YearListCell.ClassNameMonthButton);
  2309.     if (!monthButtonElement)
  2310.         return;
  2311.     var cellElement = enclosingNodeOrSelfWithClass(monthButtonElement, YearListCell.ClassNameYearListCell);
  2312.     var cell = cellElement.$view;
  2313.     this.highlightMonth(new Month(cell.row + 1, parseInt(monthButtonElement.dataset.month, 10)));
  2314. };
  2315.  
  2316. /**
  2317.  * @param {?Event} event
  2318.  */
  2319. YearListView.prototype.onMouseOver = function(event) {
  2320.     var monthButtonElement = enclosingNodeOrSelfWithClass(event.target, YearListCell.ClassNameMonthButton);
  2321.     if (!monthButtonElement)
  2322.         return;
  2323.     var cellElement = enclosingNodeOrSelfWithClass(monthButtonElement, YearListCell.ClassNameYearListCell);
  2324.     var cell = cellElement.$view;
  2325.     this.highlightMonth(new Month(cell.row + 1, parseInt(monthButtonElement.dataset.month, 10)));
  2326.     this._ignoreMouseOutUntillNextMouseOver = false;
  2327. };
  2328.  
  2329. /**
  2330.  * @param {?Event} event
  2331.  */
  2332. YearListView.prototype.onMouseOut = function(event) {
  2333.     if (this._ignoreMouseOutUntillNextMouseOver)
  2334.         return;
  2335.     var monthButtonElement = enclosingNodeOrSelfWithClass(event.target, YearListCell.ClassNameMonthButton);
  2336.     if (!monthButtonElement) {
  2337.         this.dehighlightMonth();
  2338.     }
  2339. };
  2340.  
  2341. /**
  2342.  * @param {!number} width Width in pixels.
  2343.  * @override
  2344.  */
  2345. YearListView.prototype.setWidth = function(width) {
  2346.     ListView.prototype.setWidth.call(this, width - this.scrubbyScrollBar.element.offsetWidth);
  2347.     this.element.style.width = width + "px";
  2348. };
  2349.  
  2350. /**
  2351.  * @param {!number} height Height in pixels.
  2352.  * @override
  2353.  */
  2354. YearListView.prototype.setHeight = function(height) {
  2355.     ListView.prototype.setHeight.call(this, height);
  2356.     this.scrubbyScrollBar.setHeight(height);
  2357. };
  2358.  
  2359. /**
  2360.  * @enum {number}
  2361.  */
  2362. YearListView.RowAnimationDirection = {
  2363.     Opening: 0,
  2364.     Closing: 1
  2365. };
  2366.  
  2367. /**
  2368.  * @param {!number} row
  2369.  * @param {!YearListView.RowAnimationDirection} direction
  2370.  */
  2371. YearListView.prototype._animateRow = function(row, direction) {
  2372.     var fromValue = direction === YearListView.RowAnimationDirection.Closing ? YearListCell.SelectedHeight : YearListCell.Height;
  2373.     var oldAnimator = this._runningAnimators[row];
  2374.     if (oldAnimator) {
  2375.         oldAnimator.stop();
  2376.         fromValue = oldAnimator.currentValue;
  2377.     }
  2378.     var cell = this.cellAtRow(row);
  2379.     var animator = new TransitionAnimator();
  2380.     animator.step = this.onCellHeightAnimatorStep;
  2381.     animator.setFrom(fromValue);
  2382.     animator.setTo(direction === YearListView.RowAnimationDirection.Opening ? YearListCell.SelectedHeight : YearListCell.Height);
  2383.     animator.timingFunction = AnimationTimingFunction.EaseInOut;
  2384.     animator.duration = 300;
  2385.     animator.row = row;
  2386.     animator.on(Animator.EventTypeDidAnimationStop, this.onCellHeightAnimatorDidStop);
  2387.     this._runningAnimators[row] = animator;
  2388.     this._animatingRows.push(row);
  2389.     this._animatingRows.sort();
  2390.     animator.start();
  2391. };
  2392.  
  2393. /**
  2394.  * @param {?Animator} animator
  2395.  */
  2396. YearListView.prototype.onCellHeightAnimatorDidStop = function(animator) {
  2397.     delete this._runningAnimators[animator.row];
  2398.     var index = this._animatingRows.indexOf(animator.row);
  2399.     this._animatingRows.splice(index, 1);
  2400. };
  2401.  
  2402. /**
  2403.  * @param {!Animator} animator
  2404.  */
  2405. YearListView.prototype.onCellHeightAnimatorStep = function(animator) {
  2406.     var cell = this.cellAtRow(animator.row);
  2407.     if (cell)
  2408.         cell.setHeight(animator.currentValue);
  2409.     this.updateCells();
  2410. };
  2411.  
  2412. /**
  2413.  * @param {?Event} event
  2414.  */
  2415. YearListView.prototype.onClick = function(event) {
  2416.     var oldSelectedRow = this.selectedRow;
  2417.     ListView.prototype.onClick.call(this, event);
  2418.     var year = this.selectedRow + 1;
  2419.     if (this.selectedRow !== oldSelectedRow) {
  2420.         var month = this.highlightedMonth ? this.highlightedMonth.month : 0;
  2421.         this.dispatchEvent(YearListView.EventTypeYearListViewDidSelectMonth, this, new Month(year, month));
  2422.         this.scrollView.scrollTo(this.selectedRow * YearListCell.Height, true);
  2423.     } else {
  2424.         var monthButton = enclosingNodeOrSelfWithClass(event.target, YearListCell.ClassNameMonthButton);
  2425.         if (!monthButton || monthButton.getAttribute("aria-disabled") == "true")
  2426.             return;
  2427.         var month = parseInt(monthButton.dataset.month, 10);
  2428.         this.dispatchEvent(YearListView.EventTypeYearListViewDidSelectMonth, this, new Month(year, month));
  2429.         this.hide();
  2430.     }
  2431. };
  2432.  
  2433. /**
  2434.  * @param {!number} scrollOffset
  2435.  * @return {!number}
  2436.  * @override
  2437.  */
  2438. YearListView.prototype.rowAtScrollOffset = function(scrollOffset) {
  2439.     var remainingOffset = scrollOffset;
  2440.     var lastAnimatingRow = 0;
  2441.     var rowsWithIrregularHeight = this._animatingRows.slice();
  2442.     if (this.selectedRow > -1 && !this._runningAnimators[this.selectedRow]) {
  2443.         rowsWithIrregularHeight.push(this.selectedRow);
  2444.         rowsWithIrregularHeight.sort();
  2445.     }
  2446.     for (var i = 0; i < rowsWithIrregularHeight.length; ++i) {
  2447.         var row = rowsWithIrregularHeight[i];
  2448.         var animator = this._runningAnimators[row];
  2449.         var rowHeight = animator ? animator.currentValue : YearListCell.SelectedHeight;
  2450.         if (remainingOffset <= (row - lastAnimatingRow) * YearListCell.Height) {
  2451.             return lastAnimatingRow + Math.floor(remainingOffset / YearListCell.Height);
  2452.         }
  2453.         remainingOffset -= (row - lastAnimatingRow) * YearListCell.Height;
  2454.         if (remainingOffset <= (rowHeight - YearListCell.Height))
  2455.             return row;
  2456.         remainingOffset -= rowHeight - YearListCell.Height;
  2457.         lastAnimatingRow = row;
  2458.     }
  2459.     return lastAnimatingRow + Math.floor(remainingOffset / YearListCell.Height);
  2460. };
  2461.  
  2462. /**
  2463.  * @param {!number} row
  2464.  * @return {!number}
  2465.  * @override
  2466.  */
  2467. YearListView.prototype.scrollOffsetForRow = function(row) {
  2468.     var scrollOffset = row * YearListCell.Height;
  2469.     for (var i = 0; i < this._animatingRows.length; ++i) {
  2470.         var animatingRow = this._animatingRows[i];
  2471.         if (animatingRow >= row)
  2472.             break;
  2473.         var animator = this._runningAnimators[animatingRow];
  2474.         scrollOffset += animator.currentValue - YearListCell.Height;
  2475.     }
  2476.     if (this.selectedRow > -1 && this.selectedRow < row && !this._runningAnimators[this.selectedRow]) {
  2477.         scrollOffset += YearListCell.SelectedHeight - YearListCell.Height;
  2478.     }
  2479.     return scrollOffset;
  2480. };
  2481.  
  2482. /**
  2483.  * @param {!number} row
  2484.  * @return {!YearListCell}
  2485.  * @override
  2486.  */
  2487. YearListView.prototype.prepareNewCell = function(row) {
  2488.     var cell = YearListCell._recycleBin.pop() || new YearListCell(global.params.shortMonthLabels);
  2489.     cell.reset(row);
  2490.     cell.setSelected(this.selectedRow === row);
  2491.     for (var i = 0; i < cell.monthButtons.length; ++i) {
  2492.         var month = new Month(row + 1, i);
  2493.         cell.monthButtons[i].id = month.toString();
  2494.         cell.monthButtons[i].setAttribute("aria-disabled", this._minimumMonth > month || this._maximumMonth < month ? "true" : "false");
  2495.         cell.monthButtons[i].setAttribute("aria-label", month.toLocaleString());
  2496.     }
  2497.     if (this.highlightedMonth && row === this.highlightedMonth.year - 1) {
  2498.         var monthButton = cell.monthButtons[this.highlightedMonth.month];
  2499.         monthButton.classList.add(YearListCell.ClassNameHighlighted);
  2500.         // aira-activedescendant assumes both elements have renderers, and
  2501.         // |monthButton| might have no renderer yet.
  2502.         var element = this.element;
  2503.         setTimeout(function() {
  2504.             element.setAttribute("aria-activedescendant", monthButton.id);
  2505.         }, 0);
  2506.     }
  2507.     var animator = this._runningAnimators[row];
  2508.     if (animator)
  2509.         cell.setHeight(animator.currentValue);
  2510.     else if (row === this.selectedRow)
  2511.         cell.setHeight(YearListCell.SelectedHeight);
  2512.     else
  2513.         cell.setHeight(YearListCell.Height);
  2514.     return cell;
  2515. };
  2516.  
  2517. /**
  2518.  * @override
  2519.  */
  2520. YearListView.prototype.updateCells = function() {
  2521.     var firstVisibleRow = this.firstVisibleRow();
  2522.     var lastVisibleRow = this.lastVisibleRow();
  2523.     console.assert(firstVisibleRow <= lastVisibleRow);
  2524.     for (var c in this._cells) {
  2525.         var cell = this._cells[c];
  2526.         if (cell.row < firstVisibleRow || cell.row > lastVisibleRow)
  2527.             this.throwAwayCell(cell);
  2528.     }
  2529.     for (var i = firstVisibleRow; i <= lastVisibleRow; ++i) {
  2530.         var cell = this._cells[i];
  2531.         if (cell)
  2532.             cell.setPosition(this.scrollView.contentPositionForContentOffset(this.scrollOffsetForRow(cell.row)));
  2533.         else
  2534.             this.addCellIfNecessary(i);
  2535.     }
  2536.     this.setNeedsUpdateCells(false);
  2537. };
  2538.  
  2539. /**
  2540.  * @override
  2541.  */
  2542. YearListView.prototype.deselect = function() {
  2543.     if (this.selectedRow === ListView.NoSelection)
  2544.         return;
  2545.     var selectedCell = this._cells[this.selectedRow];
  2546.     if (selectedCell)
  2547.         selectedCell.setSelected(false);
  2548.     this._animateRow(this.selectedRow, YearListView.RowAnimationDirection.Closing);
  2549.     this.selectedRow = ListView.NoSelection;
  2550.     this.setNeedsUpdateCells(true);
  2551. };
  2552.  
  2553. YearListView.prototype.deselectWithoutAnimating = function() {
  2554.     if (this.selectedRow === ListView.NoSelection)
  2555.         return;
  2556.     var selectedCell = this._cells[this.selectedRow];
  2557.     if (selectedCell) {
  2558.         selectedCell.setSelected(false);
  2559.         selectedCell.setHeight(YearListCell.Height);
  2560.     }
  2561.     this.selectedRow = ListView.NoSelection;
  2562.     this.setNeedsUpdateCells(true);
  2563. };
  2564.  
  2565. /**
  2566.  * @param {!number} row
  2567.  * @override
  2568.  */
  2569. YearListView.prototype.select = function(row) {
  2570.     if (this.selectedRow === row)
  2571.         return;
  2572.     this.deselect();
  2573.     if (row === ListView.NoSelection)
  2574.         return;
  2575.     this.selectedRow = row;
  2576.     if (this.selectedRow !== ListView.NoSelection) {
  2577.         var selectedCell = this._cells[this.selectedRow];
  2578.         this._animateRow(this.selectedRow, YearListView.RowAnimationDirection.Opening);
  2579.         if (selectedCell)
  2580.             selectedCell.setSelected(true);
  2581.         var month = this.highlightedMonth ? this.highlightedMonth.month : 0;
  2582.         this.highlightMonth(new Month(this.selectedRow + 1, month));
  2583.     }
  2584.     this.setNeedsUpdateCells(true);
  2585. };
  2586.  
  2587. /**
  2588.  * @param {!number} row
  2589.  */
  2590. YearListView.prototype.selectWithoutAnimating = function(row) {
  2591.     if (this.selectedRow === row)
  2592.         return;
  2593.     this.deselectWithoutAnimating();
  2594.     if (row === ListView.NoSelection)
  2595.         return;
  2596.     this.selectedRow = row;
  2597.     if (this.selectedRow !== ListView.NoSelection) {
  2598.         var selectedCell = this._cells[this.selectedRow];
  2599.         if (selectedCell) {
  2600.             selectedCell.setSelected(true);
  2601.             selectedCell.setHeight(YearListCell.SelectedHeight);
  2602.         }
  2603.         var month = this.highlightedMonth ? this.highlightedMonth.month : 0;
  2604.         this.highlightMonth(new Month(this.selectedRow + 1, month));
  2605.     }
  2606.     this.setNeedsUpdateCells(true);
  2607. };
  2608.  
  2609. /**
  2610.  * @param {!Month} month
  2611.  * @return {?HTMLDivElement}
  2612.  */
  2613. YearListView.prototype.buttonForMonth = function(month) {
  2614.     if (!month)
  2615.         return null;
  2616.     var row = month.year - 1;
  2617.     var cell = this.cellAtRow(row);
  2618.     if (!cell)
  2619.         return null;
  2620.     return cell.monthButtons[month.month];
  2621. };
  2622.  
  2623. YearListView.prototype.dehighlightMonth = function() {
  2624.     if (!this.highlightedMonth)
  2625.         return;
  2626.     var monthButton = this.buttonForMonth(this.highlightedMonth);
  2627.     if (monthButton) {
  2628.         monthButton.classList.remove(YearListCell.ClassNameHighlighted);
  2629.     }
  2630.     this.highlightedMonth = null;
  2631.     this.element.removeAttribute("aria-activedescendant");
  2632. };
  2633.  
  2634. /**
  2635.  * @param {!Month} month
  2636.  */
  2637. YearListView.prototype.highlightMonth = function(month) {
  2638.     if (this.highlightedMonth && this.highlightedMonth.equals(month))
  2639.         return;
  2640.     this.dehighlightMonth();
  2641.     this.highlightedMonth = month;
  2642.     if (!this.highlightedMonth)
  2643.         return;
  2644.     var monthButton = this.buttonForMonth(this.highlightedMonth);
  2645.     if (monthButton) {
  2646.         monthButton.classList.add(YearListCell.ClassNameHighlighted);
  2647.         this.element.setAttribute("aria-activedescendant", monthButton.id);
  2648.     }
  2649. };
  2650.  
  2651. /**
  2652.  * @param {!Month} month
  2653.  */
  2654. YearListView.prototype.show = function(month) {
  2655.     this._ignoreMouseOutUntillNextMouseOver = true;
  2656.     
  2657.     this.scrollToRow(month.year - 1, false);
  2658.     this.selectWithoutAnimating(month.year - 1);
  2659.     this.highlightMonth(month);
  2660. };
  2661.  
  2662. YearListView.prototype.hide = function() {
  2663.     this.dispatchEvent(YearListView.EventTypeYearListViewDidHide, this);
  2664. };
  2665.  
  2666. /**
  2667.  * @param {!Month} month
  2668.  */
  2669. YearListView.prototype._moveHighlightTo = function(month) {
  2670.     this.highlightMonth(month);
  2671.     this.select(this.highlightedMonth.year - 1);
  2672.  
  2673.     this.dispatchEvent(YearListView.EventTypeYearListViewDidSelectMonth, this, month);
  2674.     this.scrollView.scrollTo(this.selectedRow * YearListCell.Height, true);
  2675.     return true;
  2676. };
  2677.  
  2678. /**
  2679.  * @param {?Event} event
  2680.  */
  2681. YearListView.prototype.onKeyDown = function(event) {
  2682.     var key = event.keyIdentifier;
  2683.     var eventHandled = false;
  2684.     if (key == "U+0054") // 't' key.
  2685.         eventHandled = this._moveHighlightTo(Month.createFromToday());
  2686.     else if (this.highlightedMonth) {
  2687.         if (global.params.isLocaleRTL ? key == "Right" : key == "Left")
  2688.             eventHandled = this._moveHighlightTo(this.highlightedMonth.previous());
  2689.         else if (key == "Up")
  2690.             eventHandled = this._moveHighlightTo(this.highlightedMonth.previous(YearListCell.ButtonColumns));
  2691.         else if (global.params.isLocaleRTL ? key == "Left" : key == "Right")
  2692.             eventHandled = this._moveHighlightTo(this.highlightedMonth.next());
  2693.         else if (key == "Down")
  2694.             eventHandled = this._moveHighlightTo(this.highlightedMonth.next(YearListCell.ButtonColumns));
  2695.         else if (key == "PageUp")
  2696.             eventHandled = this._moveHighlightTo(this.highlightedMonth.previous(MonthsPerYear));
  2697.         else if (key == "PageDown")
  2698.             eventHandled = this._moveHighlightTo(this.highlightedMonth.next(MonthsPerYear));
  2699.         else if (key == "Enter") {
  2700.             this.dispatchEvent(YearListView.EventTypeYearListViewDidSelectMonth, this, this.highlightedMonth);
  2701.             this.hide();
  2702.             eventHandled = true;
  2703.         }
  2704.     } else if (key == "Up") {
  2705.         this.scrollView.scrollBy(-YearListCell.Height, true);
  2706.         eventHandled = true;
  2707.     } else if (key == "Down") {
  2708.         this.scrollView.scrollBy(YearListCell.Height, true);
  2709.         eventHandled = true;
  2710.     } else if (key == "PageUp") {
  2711.         this.scrollView.scrollBy(-this.scrollView.height(), true);
  2712.         eventHandled = true;
  2713.     } else if (key == "PageDown") {
  2714.         this.scrollView.scrollBy(this.scrollView.height(), true);
  2715.         eventHandled = true;
  2716.     }
  2717.  
  2718.     if (eventHandled) {
  2719.         event.stopPropagation();
  2720.         event.preventDefault();
  2721.     }
  2722. };
  2723.  
  2724. /**
  2725.  * @constructor
  2726.  * @extends View
  2727.  * @param {!Month} minimumMonth
  2728.  * @param {!Month} maximumMonth
  2729.  */
  2730. function MonthPopupView(minimumMonth, maximumMonth) {
  2731.     View.call(this, createElement("div", MonthPopupView.ClassNameMonthPopupView));
  2732.  
  2733.     /**
  2734.      * @type {!YearListView}
  2735.      * @const
  2736.      */
  2737.     this.yearListView = new YearListView(minimumMonth, maximumMonth);
  2738.     this.yearListView.attachTo(this);
  2739.  
  2740.     /**
  2741.      * @type {!boolean}
  2742.      */
  2743.     this.isVisible = false;
  2744.  
  2745.     this.element.addEventListener("click", this.onClick, false);
  2746. }
  2747.  
  2748. MonthPopupView.prototype = Object.create(View.prototype);
  2749.  
  2750. MonthPopupView.ClassNameMonthPopupView = "month-popup-view";
  2751.  
  2752. MonthPopupView.prototype.show = function(initialMonth, calendarTableRect) {
  2753.     this.isVisible = true;
  2754.     document.body.appendChild(this.element);
  2755.     this.yearListView.setWidth(calendarTableRect.width - 2);
  2756.     this.yearListView.setHeight(YearListView.Height);
  2757.     if (global.params.isLocaleRTL)
  2758.         this.yearListView.element.style.right = calendarTableRect.x + "px";
  2759.     else
  2760.         this.yearListView.element.style.left = calendarTableRect.x + "px";
  2761.     this.yearListView.element.style.top = calendarTableRect.y + "px";
  2762.     this.yearListView.show(initialMonth);
  2763.     this.yearListView.element.focus();
  2764. };
  2765.  
  2766. MonthPopupView.prototype.hide = function() {
  2767.     if (!this.isVisible)
  2768.         return;
  2769.     this.isVisible = false;
  2770.     this.element.parentNode.removeChild(this.element);
  2771.     this.yearListView.hide();
  2772. };
  2773.  
  2774. /**
  2775.  * @param {?Event} event
  2776.  */
  2777. MonthPopupView.prototype.onClick = function(event) {
  2778.     if (event.target !== this.element)
  2779.         return;
  2780.     this.hide();
  2781. };
  2782.  
  2783. /**
  2784.  * @constructor
  2785.  * @extends View
  2786.  * @param {!number} maxWidth Maximum width in pixels.
  2787.  */
  2788. function MonthPopupButton(maxWidth) {
  2789.     View.call(this, createElement("button", MonthPopupButton.ClassNameMonthPopupButton));
  2790.     this.element.setAttribute("aria-label", global.params.axShowMonthSelector);
  2791.  
  2792.     /**
  2793.      * @type {!Element}
  2794.      * @const
  2795.      */
  2796.     this.labelElement = createElement("span", MonthPopupButton.ClassNameMonthPopupButtonLabel, "-----");
  2797.     this.element.appendChild(this.labelElement);
  2798.  
  2799.     /**
  2800.      * @type {!Element}
  2801.      * @const
  2802.      */
  2803.     this.disclosureTriangleIcon = createElement("span", MonthPopupButton.ClassNameDisclosureTriangle);
  2804.     this.disclosureTriangleIcon.innerHTML = "<svg width='7' height='5'><polygon points='0,1 7,1 3.5,5' style='fill:#000000;' /></svg>";
  2805.     this.element.appendChild(this.disclosureTriangleIcon);
  2806.  
  2807.     /**
  2808.      * @type {!boolean}
  2809.      * @protected
  2810.      */
  2811.     this._useShortMonth = this._shouldUseShortMonth(maxWidth);
  2812.     this.element.style.maxWidth = maxWidth + "px";
  2813.  
  2814.     this.element.addEventListener("click", this.onClick, false);
  2815. }
  2816.  
  2817. MonthPopupButton.prototype = Object.create(View.prototype);
  2818.  
  2819. MonthPopupButton.ClassNameMonthPopupButton = "month-popup-button";
  2820. MonthPopupButton.ClassNameMonthPopupButtonLabel = "month-popup-button-label";
  2821. MonthPopupButton.ClassNameDisclosureTriangle = "disclosure-triangle";
  2822. MonthPopupButton.EventTypeButtonClick = "buttonClick";
  2823.  
  2824. /**
  2825.  * @param {!number} maxWidth Maximum available width in pixels.
  2826.  * @return {!boolean}
  2827.  */
  2828. MonthPopupButton.prototype._shouldUseShortMonth = function(maxWidth) {
  2829.     document.body.appendChild(this.element);
  2830.     var month = Month.Maximum;
  2831.     for (var i = 0; i < MonthsPerYear; ++i) {
  2832.         this.labelElement.textContent = month.toLocaleString();
  2833.         if (this.element.offsetWidth > maxWidth)
  2834.             return true;
  2835.         month = month.previous();
  2836.     }
  2837.     document.body.removeChild(this.element);
  2838.     return false;
  2839. };
  2840.  
  2841. /**
  2842.  * @param {!Month} month
  2843.  */
  2844. MonthPopupButton.prototype.setCurrentMonth = function(month) {
  2845.     this.labelElement.textContent = this._useShortMonth ? month.toShortLocaleString() : month.toLocaleString();
  2846. };
  2847.  
  2848. /**
  2849.  * @param {?Event} event
  2850.  */
  2851. MonthPopupButton.prototype.onClick = function(event) {
  2852.     this.dispatchEvent(MonthPopupButton.EventTypeButtonClick, this);
  2853. };
  2854.  
  2855. /**
  2856.  * @constructor
  2857.  * @extends View
  2858.  */
  2859. function CalendarNavigationButton() {
  2860.     View.call(this, createElement("button", CalendarNavigationButton.ClassNameCalendarNavigationButton));
  2861.     /**
  2862.      * @type {number} Threshold for starting repeating clicks in milliseconds.
  2863.      */
  2864.     this.repeatingClicksStartingThreshold = CalendarNavigationButton.DefaultRepeatingClicksStartingThreshold;
  2865.     /**
  2866.      * @type {number} Interval between reapeating clicks in milliseconds.
  2867.      */
  2868.     this.reapeatingClicksInterval = CalendarNavigationButton.DefaultRepeatingClicksInterval;
  2869.     /**
  2870.      * @type {?number} The ID for the timeout that triggers the repeating clicks.
  2871.      */
  2872.     this._timer = null;
  2873.     this.element.addEventListener("click", this.onClick, false);
  2874.     this.element.addEventListener("mousedown", this.onMouseDown, false);
  2875.     this.element.addEventListener("touchstart", this.onTouchStart, false);
  2876. };
  2877.  
  2878. CalendarNavigationButton.prototype = Object.create(View.prototype);
  2879.  
  2880. CalendarNavigationButton.DefaultRepeatingClicksStartingThreshold = 600;
  2881. CalendarNavigationButton.DefaultRepeatingClicksInterval = 300;
  2882. CalendarNavigationButton.LeftMargin = 4;
  2883. CalendarNavigationButton.Width = 24;
  2884. CalendarNavigationButton.ClassNameCalendarNavigationButton = "calendar-navigation-button";
  2885. CalendarNavigationButton.EventTypeButtonClick = "buttonClick";
  2886. CalendarNavigationButton.EventTypeRepeatingButtonClick = "repeatingButtonClick";
  2887.  
  2888. /**
  2889.  * @param {!boolean} disabled
  2890.  */
  2891. CalendarNavigationButton.prototype.setDisabled = function(disabled) {
  2892.     this.element.disabled = disabled;
  2893. };
  2894.  
  2895. /**
  2896.  * @param {?Event} event
  2897.  */
  2898. CalendarNavigationButton.prototype.onClick = function(event) {
  2899.     this.dispatchEvent(CalendarNavigationButton.EventTypeButtonClick, this);
  2900. };
  2901.  
  2902. /**
  2903.  * @param {?Event} event
  2904.  */
  2905. CalendarNavigationButton.prototype.onTouchStart = function(event) {
  2906.     if (this._timer !== null)
  2907.         return;
  2908.     this._timer = setTimeout(this.onRepeatingClick, this.repeatingClicksStartingThreshold);
  2909.     window.addEventListener("touchend", this.onWindowTouchEnd, false);
  2910. };
  2911.  
  2912. /**
  2913.  * @param {?Event} event
  2914.  */
  2915. CalendarNavigationButton.prototype.onWindowTouchEnd = function(event) {
  2916.     if (this._timer === null)
  2917.         return;
  2918.     clearTimeout(this._timer);
  2919.     this._timer = null;
  2920.     window.removeEventListener("touchend", this.onWindowMouseUp, false);
  2921. };
  2922.  
  2923. /**
  2924.  * @param {?Event} event
  2925.  */
  2926. CalendarNavigationButton.prototype.onMouseDown = function(event) {
  2927.     if (this._timer !== null)
  2928.         return;
  2929.     this._timer = setTimeout(this.onRepeatingClick, this.repeatingClicksStartingThreshold);
  2930.     window.addEventListener("mouseup", this.onWindowMouseUp, false);
  2931. };
  2932.  
  2933. /**
  2934.  * @param {?Event} event
  2935.  */
  2936. CalendarNavigationButton.prototype.onWindowMouseUp = function(event) {
  2937.     if (this._timer === null)
  2938.         return;
  2939.     clearTimeout(this._timer);
  2940.     this._timer = null;
  2941.     window.removeEventListener("mouseup", this.onWindowMouseUp, false);
  2942. };
  2943.  
  2944. /**
  2945.  * @param {?Event} event
  2946.  */
  2947. CalendarNavigationButton.prototype.onRepeatingClick = function(event) {
  2948.     this.dispatchEvent(CalendarNavigationButton.EventTypeRepeatingButtonClick, this);
  2949.     this._timer = setTimeout(this.onRepeatingClick, this.reapeatingClicksInterval);
  2950. };
  2951.  
  2952. /**
  2953.  * @constructor
  2954.  * @extends View
  2955.  * @param {!CalendarPicker} calendarPicker
  2956.  */
  2957. function CalendarHeaderView(calendarPicker) {
  2958.     View.call(this, createElement("div", CalendarHeaderView.ClassNameCalendarHeaderView));
  2959.     this.calendarPicker = calendarPicker;
  2960.     this.calendarPicker.on(CalendarPicker.EventTypeCurrentMonthChanged, this.onCurrentMonthChanged);
  2961.     
  2962.     var titleElement = createElement("div", CalendarHeaderView.ClassNameCalendarTitle);
  2963.     this.element.appendChild(titleElement);
  2964.  
  2965.     /**
  2966.      * @type {!MonthPopupButton}
  2967.      */
  2968.     this.monthPopupButton = new MonthPopupButton(this.calendarPicker.calendarTableView.width() - CalendarTableView.BorderWidth * 2 - CalendarNavigationButton.Width * 3 - CalendarNavigationButton.LeftMargin * 2);
  2969.     this.monthPopupButton.attachTo(titleElement);
  2970.  
  2971.     /**
  2972.      * @type {!CalendarNavigationButton}
  2973.      * @const
  2974.      */
  2975.     this._previousMonthButton = new CalendarNavigationButton();
  2976.     this._previousMonthButton.attachTo(this);
  2977.     this._previousMonthButton.on(CalendarNavigationButton.EventTypeButtonClick, this.onNavigationButtonClick);
  2978.     this._previousMonthButton.on(CalendarNavigationButton.EventTypeRepeatingButtonClick, this.onNavigationButtonClick);
  2979.     this._previousMonthButton.element.setAttribute("aria-label", global.params.axShowPreviousMonth);
  2980.  
  2981.     /**
  2982.      * @type {!CalendarNavigationButton}
  2983.      * @const
  2984.      */
  2985.     this._todayButton = new CalendarNavigationButton();
  2986.     this._todayButton.attachTo(this);
  2987.     this._todayButton.on(CalendarNavigationButton.EventTypeButtonClick, this.onNavigationButtonClick);
  2988.     this._todayButton.element.classList.add(CalendarHeaderView.ClassNameTodayButton);
  2989.     var monthContainingToday = Month.createFromToday();
  2990.     this._todayButton.setDisabled(monthContainingToday < this.calendarPicker.minimumMonth || monthContainingToday > this.calendarPicker.maximumMonth);
  2991.     this._todayButton.element.setAttribute("aria-label", global.params.todayLabel);
  2992.  
  2993.     /**
  2994.      * @type {!CalendarNavigationButton}
  2995.      * @const
  2996.      */
  2997.     this._nextMonthButton = new CalendarNavigationButton();
  2998.     this._nextMonthButton.attachTo(this);
  2999.     this._nextMonthButton.on(CalendarNavigationButton.EventTypeButtonClick, this.onNavigationButtonClick);
  3000.     this._nextMonthButton.on(CalendarNavigationButton.EventTypeRepeatingButtonClick, this.onNavigationButtonClick);
  3001.     this._nextMonthButton.element.setAttribute("aria-label", global.params.axShowNextMonth);
  3002.  
  3003.     if (global.params.isLocaleRTL) {
  3004.         this._nextMonthButton.element.innerHTML = CalendarHeaderView._BackwardTriangle;
  3005.         this._previousMonthButton.element.innerHTML = CalendarHeaderView._ForwardTriangle;
  3006.     } else {
  3007.         this._nextMonthButton.element.innerHTML = CalendarHeaderView._ForwardTriangle;
  3008.         this._previousMonthButton.element.innerHTML = CalendarHeaderView._BackwardTriangle;
  3009.     }
  3010. }
  3011.  
  3012. CalendarHeaderView.prototype = Object.create(View.prototype);
  3013.  
  3014. CalendarHeaderView.Height = 24;
  3015. CalendarHeaderView.BottomMargin = 10;
  3016. CalendarHeaderView._ForwardTriangle = "<svg width='4' height='7'><polygon points='0,7 0,0, 4,3.5' style='fill:#6e6e6e;' /></svg>";
  3017. CalendarHeaderView._BackwardTriangle = "<svg width='4' height='7'><polygon points='0,3.5 4,7 4,0' style='fill:#6e6e6e;' /></svg>";
  3018. CalendarHeaderView.ClassNameCalendarHeaderView = "calendar-header-view";
  3019. CalendarHeaderView.ClassNameCalendarTitle = "calendar-title";
  3020. CalendarHeaderView.ClassNameTodayButton = "today-button";
  3021.  
  3022. CalendarHeaderView.prototype.onCurrentMonthChanged = function() {
  3023.     this.monthPopupButton.setCurrentMonth(this.calendarPicker.currentMonth());
  3024.     this._previousMonthButton.setDisabled(this.disabled || this.calendarPicker.currentMonth() <= this.calendarPicker.minimumMonth);
  3025.     this._nextMonthButton.setDisabled(this.disabled || this.calendarPicker.currentMonth() >= this.calendarPicker.maximumMonth);
  3026. };
  3027.  
  3028. CalendarHeaderView.prototype.onNavigationButtonClick = function(sender) {
  3029.     if (sender === this._previousMonthButton)
  3030.         this.calendarPicker.setCurrentMonth(this.calendarPicker.currentMonth().previous(), CalendarPicker.NavigationBehavior.WithAnimation);
  3031.     else if (sender === this._nextMonthButton)
  3032.         this.calendarPicker.setCurrentMonth(this.calendarPicker.currentMonth().next(), CalendarPicker.NavigationBehavior.WithAnimation);
  3033.     else
  3034.         this.calendarPicker.selectRangeContainingDay(Day.createFromToday());
  3035. };
  3036.  
  3037. /**
  3038.  * @param {!boolean} disabled
  3039.  */
  3040. CalendarHeaderView.prototype.setDisabled = function(disabled) {
  3041.     this.disabled = disabled;
  3042.     this.monthPopupButton.element.disabled = this.disabled;
  3043.     this._previousMonthButton.setDisabled(this.disabled || this.calendarPicker.currentMonth() <= this.calendarPicker.minimumMonth);
  3044.     this._nextMonthButton.setDisabled(this.disabled || this.calendarPicker.currentMonth() >= this.calendarPicker.maximumMonth);
  3045.     var monthContainingToday = Month.createFromToday();
  3046.     this._todayButton.setDisabled(this.disabled || monthContainingToday < this.calendarPicker.minimumMonth || monthContainingToday > this.calendarPicker.maximumMonth);
  3047. };
  3048.  
  3049. /**
  3050.  * @constructor
  3051.  * @extends ListCell
  3052.  */
  3053. function DayCell() {
  3054.     ListCell.call(this);
  3055.     this.element.classList.add(DayCell.ClassNameDayCell);
  3056.     this.element.style.width = DayCell.Width + "px";
  3057.     this.element.style.height = DayCell.Height + "px";
  3058.     this.element.style.lineHeight = (DayCell.Height - DayCell.PaddingSize * 2) + "px";
  3059.     this.element.setAttribute("role", "gridcell");
  3060.     /**
  3061.      * @type {?Day}
  3062.      */
  3063.     this.day = null;
  3064. };
  3065.  
  3066. DayCell.prototype = Object.create(ListCell.prototype);
  3067.  
  3068. DayCell.Width = 34;
  3069. DayCell.Height = hasInaccuratePointingDevice() ? 34 : 20;
  3070. DayCell.PaddingSize = 1;
  3071. DayCell.ClassNameDayCell = "day-cell";
  3072. DayCell.ClassNameHighlighted = "highlighted";
  3073. DayCell.ClassNameDisabled = "disabled";
  3074. DayCell.ClassNameCurrentMonth = "current-month";
  3075. DayCell.ClassNameToday = "today";
  3076.  
  3077. DayCell._recycleBin = [];
  3078.  
  3079. DayCell.recycleOrCreate = function() {
  3080.     return DayCell._recycleBin.pop() || new DayCell();
  3081. };
  3082.  
  3083. /**
  3084.  * @return {!Array}
  3085.  * @override
  3086.  */
  3087. DayCell.prototype._recycleBin = function() {
  3088.     return DayCell._recycleBin;
  3089. };
  3090.  
  3091. /**
  3092.  * @override
  3093.  */
  3094. DayCell.prototype.throwAway = function() {
  3095.     ListCell.prototype.throwAway.call(this);
  3096.     this.day = null;
  3097. };
  3098.  
  3099. /**
  3100.  * @param {!boolean} highlighted
  3101.  */
  3102. DayCell.prototype.setHighlighted = function(highlighted) {
  3103.     if (highlighted) {
  3104.         this.element.classList.add(DayCell.ClassNameHighlighted);
  3105.         this.element.setAttribute("aria-selected", "true");
  3106.     } else {
  3107.         this.element.classList.remove(DayCell.ClassNameHighlighted);
  3108.         this.element.setAttribute("aria-selected", "false");
  3109.     }
  3110. };
  3111.  
  3112. /**
  3113.  * @param {!boolean} disabled
  3114.  */
  3115. DayCell.prototype.setDisabled = function(disabled) {
  3116.     if (disabled)
  3117.         this.element.classList.add(DayCell.ClassNameDisabled);
  3118.     else
  3119.         this.element.classList.remove(DayCell.ClassNameDisabled);
  3120. };
  3121.  
  3122. /**
  3123.  * @param {!boolean} selected
  3124.  */
  3125. DayCell.prototype.setIsInCurrentMonth = function(selected) {
  3126.     if (selected)
  3127.         this.element.classList.add(DayCell.ClassNameCurrentMonth);
  3128.     else
  3129.         this.element.classList.remove(DayCell.ClassNameCurrentMonth);
  3130. };
  3131.  
  3132. /**
  3133.  * @param {!boolean} selected
  3134.  */
  3135. DayCell.prototype.setIsToday = function(selected) {
  3136.     if (selected)
  3137.         this.element.classList.add(DayCell.ClassNameToday);
  3138.     else
  3139.         this.element.classList.remove(DayCell.ClassNameToday);
  3140. };
  3141.  
  3142. /**
  3143.  * @param {!Day} day
  3144.  */
  3145. DayCell.prototype.reset = function(day) {
  3146.     this.day = day;
  3147.     this.element.textContent = localizeNumber(this.day.date.toString());
  3148.     this.element.setAttribute("aria-label", this.day.format());
  3149.     this.element.id = this.day.toString();
  3150.     this.show();
  3151. };
  3152.  
  3153. /**
  3154.  * @constructor
  3155.  * @extends ListCell
  3156.  */
  3157. function WeekNumberCell() {
  3158.     ListCell.call(this);
  3159.     this.element.classList.add(WeekNumberCell.ClassNameWeekNumberCell);
  3160.     this.element.style.width = (WeekNumberCell.Width - WeekNumberCell.SeparatorWidth) + "px";
  3161.     this.element.style.height = WeekNumberCell.Height + "px";
  3162.     this.element.style.lineHeight = (WeekNumberCell.Height - WeekNumberCell.PaddingSize * 2) + "px";
  3163.     /**
  3164.      * @type {?Week}
  3165.      */
  3166.     this.week = null;
  3167. };
  3168.  
  3169. WeekNumberCell.prototype = Object.create(ListCell.prototype);
  3170.  
  3171. WeekNumberCell.Width = 48;
  3172. WeekNumberCell.Height = DayCell.Height;
  3173. WeekNumberCell.SeparatorWidth = 1;
  3174. WeekNumberCell.PaddingSize = 1;
  3175. WeekNumberCell.ClassNameWeekNumberCell = "week-number-cell";
  3176. WeekNumberCell.ClassNameHighlighted = "highlighted";
  3177. WeekNumberCell.ClassNameDisabled = "disabled";
  3178.  
  3179. WeekNumberCell._recycleBin = [];
  3180.  
  3181. /**
  3182.  * @return {!Array}
  3183.  * @override
  3184.  */
  3185. WeekNumberCell.prototype._recycleBin = function() {
  3186.     return WeekNumberCell._recycleBin;
  3187. };
  3188.  
  3189. /**
  3190.  * @return {!WeekNumberCell}
  3191.  */
  3192. WeekNumberCell.recycleOrCreate = function() {
  3193.     return WeekNumberCell._recycleBin.pop() || new WeekNumberCell();
  3194. };
  3195.  
  3196. /**
  3197.  * @param {!Week} week
  3198.  */
  3199. WeekNumberCell.prototype.reset = function(week) {
  3200.     this.week = week;
  3201.     this.element.id = week.toString();
  3202.     this.element.setAttribute("role", "gridcell");
  3203.     this.element.setAttribute("aria-label", window.pagePopupController.formatWeek(week.year, week.week, week.firstDay().format()));
  3204.     this.element.textContent = localizeNumber(this.week.week.toString());
  3205.     this.show();
  3206. };
  3207.  
  3208. /**
  3209.  * @override
  3210.  */
  3211. WeekNumberCell.prototype.throwAway = function() {
  3212.     ListCell.prototype.throwAway.call(this);
  3213.     this.week = null;
  3214. };
  3215.  
  3216. WeekNumberCell.prototype.setHighlighted = function(highlighted) {
  3217.     if (highlighted) {
  3218.         this.element.classList.add(WeekNumberCell.ClassNameHighlighted);
  3219.         this.element.setAttribute("aria-selected", "true");
  3220.     } else {
  3221.         this.element.classList.remove(WeekNumberCell.ClassNameHighlighted);
  3222.         this.element.setAttribute("aria-selected", "false");
  3223.     }
  3224. };
  3225.  
  3226. WeekNumberCell.prototype.setDisabled = function(disabled) {
  3227.     if (disabled)
  3228.         this.element.classList.add(WeekNumberCell.ClassNameDisabled);
  3229.     else
  3230.         this.element.classList.remove(WeekNumberCell.ClassNameDisabled);
  3231. };
  3232.  
  3233. /**
  3234.  * @constructor
  3235.  * @extends View
  3236.  * @param {!boolean} hasWeekNumberColumn
  3237.  */
  3238. function CalendarTableHeaderView(hasWeekNumberColumn) {
  3239.     View.call(this, createElement("div", "calendar-table-header-view"));
  3240.     if (hasWeekNumberColumn) {
  3241.         var weekNumberLabelElement = createElement("div", "week-number-label", global.params.weekLabel);
  3242.         weekNumberLabelElement.style.width = WeekNumberCell.Width + "px";
  3243.         this.element.appendChild(weekNumberLabelElement);
  3244.     }
  3245.     for (var i = 0; i < DaysPerWeek; ++i) {
  3246.         var weekDayNumber = (global.params.weekStartDay + i) % DaysPerWeek;
  3247.         var labelElement = createElement("div", "week-day-label", global.params.dayLabels[weekDayNumber]);
  3248.         labelElement.style.width = DayCell.Width + "px";
  3249.         this.element.appendChild(labelElement);
  3250.         if (getLanguage() === "ja") {
  3251.             if (weekDayNumber === 0)
  3252.                 labelElement.style.color = "red";
  3253.             else if (weekDayNumber === 6)
  3254.                 labelElement.style.color = "blue";
  3255.         }
  3256.     }
  3257. }
  3258.  
  3259. CalendarTableHeaderView.prototype = Object.create(View.prototype);
  3260.  
  3261. CalendarTableHeaderView.Height = 25;
  3262.  
  3263. /**
  3264.  * @constructor
  3265.  * @extends ListCell
  3266.  */
  3267. function CalendarRowCell() {
  3268.     ListCell.call(this);
  3269.     this.element.classList.add(CalendarRowCell.ClassNameCalendarRowCell);
  3270.     this.element.style.height = CalendarRowCell.Height + "px";
  3271.     this.element.setAttribute("role", "row");
  3272.  
  3273.     /**
  3274.      * @type {!Array}
  3275.      * @protected
  3276.      */
  3277.     this._dayCells = [];
  3278.     /**
  3279.      * @type {!number}
  3280.      */
  3281.     this.row = 0;
  3282.     /**
  3283.      * @type {?CalendarTableView}
  3284.      */
  3285.     this.calendarTableView = null;
  3286. }
  3287.  
  3288. CalendarRowCell.prototype = Object.create(ListCell.prototype);
  3289.  
  3290. CalendarRowCell.Height = DayCell.Height;
  3291. CalendarRowCell.ClassNameCalendarRowCell = "calendar-row-cell";
  3292.  
  3293. CalendarRowCell._recycleBin = [];
  3294.  
  3295. /**
  3296.  * @return {!Array}
  3297.  * @override
  3298.  */
  3299. CalendarRowCell.prototype._recycleBin = function() {
  3300.     return CalendarRowCell._recycleBin;
  3301. };
  3302.  
  3303. /**
  3304.  * @param {!number} row
  3305.  * @param {!CalendarTableView} calendarTableView
  3306.  */
  3307. CalendarRowCell.prototype.reset = function(row, calendarTableView) {
  3308.     this.row = row;
  3309.     this.calendarTableView = calendarTableView;
  3310.     if (this.calendarTableView.hasWeekNumberColumn) {
  3311.         var middleDay = this.calendarTableView.dayAtColumnAndRow(3, row);
  3312.         var week = Week.createFromDay(middleDay);
  3313.         this.weekNumberCell = this.calendarTableView.prepareNewWeekNumberCell(week);
  3314.         this.weekNumberCell.attachTo(this);
  3315.     }
  3316.     var day = calendarTableView.dayAtColumnAndRow(0, row);
  3317.     for (var i = 0; i < DaysPerWeek; ++i) {
  3318.         var dayCell = this.calendarTableView.prepareNewDayCell(day);
  3319.         dayCell.attachTo(this);
  3320.         this._dayCells.push(dayCell);
  3321.         day = day.next();
  3322.     }
  3323.     this.show();
  3324. };
  3325.  
  3326. /**
  3327.  * @override
  3328.  */
  3329. CalendarRowCell.prototype.throwAway = function() {
  3330.     ListCell.prototype.throwAway.call(this);
  3331.     if (this.weekNumberCell)
  3332.         this.calendarTableView.throwAwayWeekNumberCell(this.weekNumberCell);
  3333.     this._dayCells.forEach(this.calendarTableView.throwAwayDayCell, this.calendarTableView);
  3334.     this._dayCells.length = 0;
  3335. };
  3336.  
  3337. /**
  3338.  * @constructor
  3339.  * @extends ListView
  3340.  * @param {!CalendarPicker} calendarPicker
  3341.  */
  3342. function CalendarTableView(calendarPicker) {
  3343.     ListView.call(this);
  3344.     this.element.classList.add(CalendarTableView.ClassNameCalendarTableView);
  3345.     this.element.tabIndex = 0;
  3346.  
  3347.     /**
  3348.      * @type {!boolean}
  3349.      * @const
  3350.      */
  3351.     this.hasWeekNumberColumn = calendarPicker.type === "week";
  3352.     /**
  3353.      * @type {!CalendarPicker}
  3354.      * @const
  3355.      */
  3356.     this.calendarPicker = calendarPicker;
  3357.     /**
  3358.      * @type {!Object}
  3359.      * @const
  3360.      */
  3361.     this._dayCells = {};
  3362.     var headerView = new CalendarTableHeaderView(this.hasWeekNumberColumn);
  3363.     headerView.attachTo(this, this.scrollView);
  3364.  
  3365.     if (this.hasWeekNumberColumn) {
  3366.         this.setWidth(DayCell.Width * DaysPerWeek + WeekNumberCell.Width);
  3367.         /**
  3368.          * @type {?Array}
  3369.          * @const
  3370.          */
  3371.         this._weekNumberCells = [];
  3372.     } else {
  3373.         this.setWidth(DayCell.Width * DaysPerWeek);
  3374.     }
  3375.     
  3376.     /**
  3377.      * @type {!boolean}
  3378.      * @protected
  3379.      */
  3380.     this._ignoreMouseOutUntillNextMouseOver = false;
  3381.  
  3382.     this.element.addEventListener("click", this.onClick, false);
  3383.     this.element.addEventListener("mouseover", this.onMouseOver, false);
  3384.     this.element.addEventListener("mouseout", this.onMouseOut, false);
  3385.  
  3386.     // You shouldn't be able to use the mouse wheel to scroll.
  3387.     this.scrollView.element.removeEventListener("mousewheel", this.scrollView.onMouseWheel, false);
  3388.     // You shouldn't be able to do gesture scroll.
  3389.     this.scrollView.element.removeEventListener("touchstart", this.scrollView.onTouchStart, false);
  3390. }
  3391.  
  3392. CalendarTableView.prototype = Object.create(ListView.prototype);
  3393.  
  3394. CalendarTableView.BorderWidth = 1;
  3395. CalendarTableView.ClassNameCalendarTableView = "calendar-table-view";
  3396.  
  3397. /**
  3398.  * @param {!number} scrollOffset
  3399.  * @return {!number}
  3400.  */
  3401. CalendarTableView.prototype.rowAtScrollOffset = function(scrollOffset) {
  3402.     return Math.floor(scrollOffset / CalendarRowCell.Height);
  3403. };
  3404.  
  3405. /**
  3406.  * @param {!number} row
  3407.  * @return {!number}
  3408.  */
  3409. CalendarTableView.prototype.scrollOffsetForRow = function(row) {
  3410.     return row * CalendarRowCell.Height;
  3411. };
  3412.  
  3413. /**
  3414.  * @param {?Event} event
  3415.  */
  3416. CalendarTableView.prototype.onClick = function(event) {
  3417.     if (this.hasWeekNumberColumn) {
  3418.         var weekNumberCellElement = enclosingNodeOrSelfWithClass(event.target, WeekNumberCell.ClassNameWeekNumberCell);
  3419.         if (weekNumberCellElement) {
  3420.             var weekNumberCell = weekNumberCellElement.$view;
  3421.             this.calendarPicker.selectRangeContainingDay(weekNumberCell.week.firstDay());
  3422.             return;
  3423.         }
  3424.     }
  3425.     var dayCellElement = enclosingNodeOrSelfWithClass(event.target, DayCell.ClassNameDayCell);
  3426.     if (!dayCellElement)
  3427.         return;
  3428.     var dayCell = dayCellElement.$view;
  3429.     this.calendarPicker.selectRangeContainingDay(dayCell.day);
  3430. };
  3431.  
  3432. /**
  3433.  * @param {?Event} event
  3434.  */
  3435. CalendarTableView.prototype.onMouseOver = function(event) {
  3436.     if (this.hasWeekNumberColumn) {
  3437.         var weekNumberCellElement = enclosingNodeOrSelfWithClass(event.target, WeekNumberCell.ClassNameWeekNumberCell);
  3438.         if (weekNumberCellElement) {
  3439.             var weekNumberCell = weekNumberCellElement.$view;
  3440.             this.calendarPicker.highlightRangeContainingDay(weekNumberCell.week.firstDay());
  3441.             this._ignoreMouseOutUntillNextMouseOver = false;
  3442.             return;
  3443.         }
  3444.     }
  3445.     var dayCellElement = enclosingNodeOrSelfWithClass(event.target, DayCell.ClassNameDayCell);
  3446.     if (!dayCellElement)
  3447.         return;
  3448.     var dayCell = dayCellElement.$view;
  3449.     this.calendarPicker.highlightRangeContainingDay(dayCell.day);
  3450.     this._ignoreMouseOutUntillNextMouseOver = false;
  3451. };
  3452.  
  3453. /**
  3454.  * @param {?Event} event
  3455.  */
  3456. CalendarTableView.prototype.onMouseOut = function(event) {
  3457.     if (this._ignoreMouseOutUntillNextMouseOver)
  3458.         return;
  3459.     var dayCellElement = enclosingNodeOrSelfWithClass(event.target, DayCell.ClassNameDayCell);
  3460.     if (!dayCellElement) {
  3461.         this.calendarPicker.highlightRangeContainingDay(null);
  3462.     }
  3463. };
  3464.  
  3465. /**
  3466.  * @param {!number} row
  3467.  * @return {!CalendarRowCell}
  3468.  */
  3469. CalendarTableView.prototype.prepareNewCell = function(row) {
  3470.     var cell = CalendarRowCell._recycleBin.pop() || new CalendarRowCell();
  3471.     cell.reset(row, this);
  3472.     return cell;
  3473. };
  3474.  
  3475. /**
  3476.  * @return {!number} Height in pixels.
  3477.  */
  3478. CalendarTableView.prototype.height = function() {
  3479.     return this.scrollView.height() + CalendarTableHeaderView.Height + CalendarTableView.BorderWidth * 2;
  3480. };
  3481.  
  3482. /**
  3483.  * @param {!number} height Height in pixels.
  3484.  */
  3485. CalendarTableView.prototype.setHeight = function(height) {
  3486.     this.scrollView.setHeight(height - CalendarTableHeaderView.Height - CalendarTableView.BorderWidth * 2);
  3487. };
  3488.  
  3489. /**
  3490.  * @param {!Month} month
  3491.  * @param {!boolean} animate
  3492.  */
  3493. CalendarTableView.prototype.scrollToMonth = function(month, animate) {
  3494.     var rowForFirstDayInMonth = this.columnAndRowForDay(month.firstDay()).row;
  3495.     this.scrollView.scrollTo(this.scrollOffsetForRow(rowForFirstDayInMonth), animate);
  3496. };
  3497.  
  3498. /**
  3499.  * @param {!number} column
  3500.  * @param {!number} row
  3501.  * @return {!Day}
  3502.  */
  3503. CalendarTableView.prototype.dayAtColumnAndRow = function(column, row) {
  3504.     var daysSinceMinimum = row * DaysPerWeek + column + global.params.weekStartDay - CalendarTableView._MinimumDayWeekDay;
  3505.     return Day.createFromValue(daysSinceMinimum * MillisecondsPerDay + CalendarTableView._MinimumDayValue);
  3506. };
  3507.  
  3508. CalendarTableView._MinimumDayValue = Day.Minimum.valueOf();
  3509. CalendarTableView._MinimumDayWeekDay = Day.Minimum.weekDay();
  3510.  
  3511. /**
  3512.  * @param {!Day} day
  3513.  * @return {!Object} Object with properties column and row.
  3514.  */
  3515. CalendarTableView.prototype.columnAndRowForDay = function(day) {
  3516.     var daysSinceMinimum = (day.valueOf() - CalendarTableView._MinimumDayValue) / MillisecondsPerDay;
  3517.     var offset = daysSinceMinimum + CalendarTableView._MinimumDayWeekDay - global.params.weekStartDay;
  3518.     var row = Math.floor(offset / DaysPerWeek);
  3519.     var column = offset - row * DaysPerWeek;
  3520.     return {
  3521.         column: column,
  3522.         row: row
  3523.     };
  3524. };
  3525.  
  3526. CalendarTableView.prototype.updateCells = function() {
  3527.     ListView.prototype.updateCells.call(this);
  3528.  
  3529.     var selection = this.calendarPicker.selection();
  3530.     var firstDayInSelection;
  3531.     var lastDayInSelection;
  3532.     if (selection) {
  3533.         firstDayInSelection = selection.firstDay().valueOf();
  3534.         lastDayInSelection = selection.lastDay().valueOf();
  3535.     } else {
  3536.         firstDayInSelection = Infinity;
  3537.         lastDayInSelection = Infinity;
  3538.     }
  3539.     var highlight = this.calendarPicker.highlight();
  3540.     var firstDayInHighlight;
  3541.     var lastDayInHighlight;
  3542.     if (highlight) {
  3543.         firstDayInHighlight = highlight.firstDay().valueOf();
  3544.         lastDayInHighlight = highlight.lastDay().valueOf();
  3545.     } else {
  3546.         firstDayInHighlight = Infinity;
  3547.         lastDayInHighlight = Infinity;
  3548.     }
  3549.     var currentMonth = this.calendarPicker.currentMonth();
  3550.     var firstDayInCurrentMonth = currentMonth.firstDay().valueOf();
  3551.     var lastDayInCurrentMonth = currentMonth.lastDay().valueOf();
  3552.     var activeCell = null;
  3553.     for (var dayString in this._dayCells) {
  3554.         var dayCell = this._dayCells[dayString];
  3555.         var day = dayCell.day;
  3556.         dayCell.setIsToday(Day.createFromToday().equals(day));
  3557.         dayCell.setSelected(day >= firstDayInSelection && day <= lastDayInSelection);
  3558.         var isHighlighted = day >= firstDayInHighlight && day <= lastDayInHighlight;
  3559.         dayCell.setHighlighted(isHighlighted);
  3560.         if (isHighlighted) {
  3561.             if (firstDayInHighlight == lastDayInHighlight)
  3562.                 activeCell = dayCell;
  3563.             else if (this.calendarPicker.type == "month" && day == firstDayInHighlight)
  3564.                 activeCell = dayCell;
  3565.         }
  3566.         dayCell.setIsInCurrentMonth(day >= firstDayInCurrentMonth && day <= lastDayInCurrentMonth);
  3567.         dayCell.setDisabled(!this.calendarPicker.isValidDay(day));
  3568.     }
  3569.     if (this.hasWeekNumberColumn) {
  3570.         for (var weekString in this._weekNumberCells) {
  3571.             var weekNumberCell = this._weekNumberCells[weekString];
  3572.             var week = weekNumberCell.week;
  3573.             var isWeekHighlighted = highlight && highlight.equals(week);
  3574.             weekNumberCell.setSelected(selection && selection.equals(week));
  3575.             weekNumberCell.setHighlighted(isWeekHighlighted);
  3576.             if (isWeekHighlighted)
  3577.                 activeCell = weekNumberCell;
  3578.             weekNumberCell.setDisabled(!this.calendarPicker.isValid(week));
  3579.         }
  3580.     }
  3581.     if (activeCell) {
  3582.         // Ensure a renderer because an element with no renderer doesn't post
  3583.         // activedescendant events. This shouldn't run in the above |for| loop
  3584.         // to avoid CSS transition.
  3585.         activeCell.element.offsetLeft;
  3586.         this.element.setAttribute("aria-activedescendant", activeCell.element.id);
  3587.     }
  3588. };
  3589.  
  3590. /**
  3591.  * @param {!Day} day
  3592.  * @return {!DayCell}
  3593.  */
  3594. CalendarTableView.prototype.prepareNewDayCell = function(day) {
  3595.     var dayCell = DayCell.recycleOrCreate();
  3596.     dayCell.reset(day);
  3597.     if (this.calendarPicker.type == "month")
  3598.         dayCell.element.setAttribute("aria-label", Month.createFromDay(day).toLocaleString());
  3599.     this._dayCells[dayCell.day.toString()] = dayCell;
  3600.     return dayCell;
  3601. };
  3602.  
  3603. /**
  3604.  * @param {!Week} week
  3605.  * @return {!WeekNumberCell}
  3606.  */
  3607. CalendarTableView.prototype.prepareNewWeekNumberCell = function(week) {
  3608.     var weekNumberCell = WeekNumberCell.recycleOrCreate();
  3609.     weekNumberCell.reset(week);
  3610.     this._weekNumberCells[weekNumberCell.week.toString()] = weekNumberCell;
  3611.     return weekNumberCell;
  3612. };
  3613.  
  3614. /**
  3615.  * @param {!DayCell} dayCell
  3616.  */
  3617. CalendarTableView.prototype.throwAwayDayCell = function(dayCell) {
  3618.     delete this._dayCells[dayCell.day.toString()];
  3619.     dayCell.throwAway();
  3620. };
  3621.  
  3622. /**
  3623.  * @param {!WeekNumberCell} weekNumberCell
  3624.  */
  3625. CalendarTableView.prototype.throwAwayWeekNumberCell = function(weekNumberCell) {
  3626.     delete this._weekNumberCells[weekNumberCell.week.toString()];
  3627.     weekNumberCell.throwAway();
  3628. };
  3629.  
  3630. /**
  3631.  * @constructor
  3632.  * @extends View
  3633.  * @param {!Object} config
  3634.  */
  3635. function CalendarPicker(type, config) {
  3636.     View.call(this, createElement("div", CalendarPicker.ClassNameCalendarPicker));
  3637.     this.element.classList.add(CalendarPicker.ClassNamePreparing);
  3638.  
  3639.     /**
  3640.      * @type {!string}
  3641.      * @const
  3642.      */
  3643.     this.type = type;
  3644.     if (this.type === "week")
  3645.         this._dateTypeConstructor = Week;
  3646.     else if (this.type === "month")
  3647.         this._dateTypeConstructor = Month;
  3648.     else
  3649.         this._dateTypeConstructor = Day;
  3650.     /**
  3651.      * @type {!Object}
  3652.      * @const
  3653.      */
  3654.     this.config = {};
  3655.     this._setConfig(config);
  3656.     /**
  3657.      * @type {!Month}
  3658.      * @const
  3659.      */
  3660.     this.minimumMonth = Month.createFromDay(this.config.minimum.firstDay());
  3661.     /**
  3662.      * @type {!Month}
  3663.      * @const
  3664.      */
  3665.     this.maximumMonth = Month.createFromDay(this.config.maximum.lastDay());
  3666.     if (global.params.isLocaleRTL)
  3667.         this.element.classList.add("rtl");
  3668.     /**
  3669.      * @type {!CalendarTableView}
  3670.      * @const
  3671.      */
  3672.     this.calendarTableView = new CalendarTableView(this);
  3673.     this.calendarTableView.hasNumberColumn = this.type === "week";
  3674.     /**
  3675.      * @type {!CalendarHeaderView}
  3676.      * @const
  3677.      */
  3678.     this.calendarHeaderView = new CalendarHeaderView(this);
  3679.     this.calendarHeaderView.monthPopupButton.on(MonthPopupButton.EventTypeButtonClick, this.onMonthPopupButtonClick);
  3680.     /**
  3681.      * @type {!MonthPopupView}
  3682.      * @const
  3683.      */
  3684.     this.monthPopupView = new MonthPopupView(this.minimumMonth, this.maximumMonth);
  3685.     this.monthPopupView.yearListView.on(YearListView.EventTypeYearListViewDidSelectMonth, this.onYearListViewDidSelectMonth);
  3686.     this.monthPopupView.yearListView.on(YearListView.EventTypeYearListViewDidHide, this.onYearListViewDidHide);
  3687.     this.calendarHeaderView.attachTo(this);
  3688.     this.calendarTableView.attachTo(this);
  3689.     /**
  3690.      * @type {!Month}
  3691.      * @protected
  3692.      */
  3693.     this._currentMonth = new Month(NaN, NaN);
  3694.     /**
  3695.      * @type {?DateType}
  3696.      * @protected
  3697.      */
  3698.     this._selection = null;
  3699.     /**
  3700.      * @type {?DateType}
  3701.      * @protected
  3702.      */
  3703.     this._highlight = null;
  3704.     this.calendarTableView.element.addEventListener("keydown", this.onCalendarTableKeyDown, false);
  3705.     document.body.addEventListener("keydown", this.onBodyKeyDown, false);
  3706.  
  3707.     window.addEventListener("resize", this.onWindowResize, false);
  3708.  
  3709.     /**
  3710.      * @type {!number}
  3711.      * @protected
  3712.      */
  3713.     this._height = -1;
  3714.  
  3715.     var initialSelection = parseDateString(config.currentValue);
  3716.     if (initialSelection) {
  3717.         this.setCurrentMonth(Month.createFromDay(initialSelection.middleDay()), CalendarPicker.NavigationBehavior.None);
  3718.         this.setSelection(initialSelection);
  3719.     } else
  3720.         this.setCurrentMonth(Month.createFromToday(), CalendarPicker.NavigationBehavior.None);
  3721. }
  3722.  
  3723. CalendarPicker.prototype = Object.create(View.prototype);
  3724.  
  3725. CalendarPicker.Padding = 10;
  3726. CalendarPicker.BorderWidth = 1;
  3727. CalendarPicker.ClassNameCalendarPicker = "calendar-picker";
  3728. CalendarPicker.ClassNamePreparing = "preparing";
  3729. CalendarPicker.EventTypeCurrentMonthChanged = "currentMonthChanged";
  3730. CalendarPicker.commitDelayMs = 100;
  3731.  
  3732. /**
  3733.  * @param {!Event} event
  3734.  */
  3735. CalendarPicker.prototype.onWindowResize = function(event) {
  3736.     this.element.classList.remove(CalendarPicker.ClassNamePreparing);
  3737.     window.removeEventListener("resize", this.onWindowResize, false);
  3738. };
  3739.  
  3740. /**
  3741.  * @param {!YearListView} sender
  3742.  */
  3743. CalendarPicker.prototype.onYearListViewDidHide = function(sender) {
  3744.     this.monthPopupView.hide();
  3745.     this.calendarHeaderView.setDisabled(false);
  3746.     this.adjustHeight();
  3747. };
  3748.  
  3749. /**
  3750.  * @param {!YearListView} sender
  3751.  * @param {!Month} month
  3752.  */
  3753. CalendarPicker.prototype.onYearListViewDidSelectMonth = function(sender, month) {
  3754.     this.setCurrentMonth(month, CalendarPicker.NavigationBehavior.None);
  3755. };
  3756.  
  3757. /**
  3758.  * @param {!View|Node} parent
  3759.  * @param {?View|Node=} before
  3760.  * @override
  3761.  */
  3762. CalendarPicker.prototype.attachTo = function(parent, before) {
  3763.     View.prototype.attachTo.call(this, parent, before);
  3764.     this.calendarTableView.element.focus();
  3765. };
  3766.  
  3767. CalendarPicker.prototype.cleanup = function() {
  3768.     window.removeEventListener("resize", this.onWindowResize, false);
  3769.     this.calendarTableView.element.removeEventListener("keydown", this.onBodyKeyDown, false);
  3770.     // Month popup view might be attached to document.body.
  3771.     this.monthPopupView.hide();
  3772. };
  3773.  
  3774. /**
  3775.  * @param {?MonthPopupButton} sender
  3776.  */
  3777. CalendarPicker.prototype.onMonthPopupButtonClick = function(sender) {
  3778.     var clientRect = this.calendarTableView.element.getBoundingClientRect();
  3779.     var calendarTableRect = new Rectangle(clientRect.left + document.body.scrollLeft, clientRect.top + document.body.scrollTop, clientRect.width, clientRect.height);
  3780.     this.monthPopupView.show(this.currentMonth(), calendarTableRect);
  3781.     this.calendarHeaderView.setDisabled(true);
  3782.     this.adjustHeight();
  3783. };
  3784.  
  3785. CalendarPicker.prototype._setConfig = function(config) {
  3786.     this.config.minimum = (typeof config.min !== "undefined" && config.min) ? parseDateString(config.min) : this._dateTypeConstructor.Minimum;
  3787.     this.config.maximum = (typeof config.max !== "undefined" && config.max) ? parseDateString(config.max) : this._dateTypeConstructor.Maximum;
  3788.     this.config.minimumValue = this.config.minimum.valueOf();
  3789.     this.config.maximumValue = this.config.maximum.valueOf();
  3790.     this.config.step = (typeof config.step !== undefined) ? Number(config.step) : this._dateTypeConstructor.DefaultStep;
  3791.     this.config.stepBase = (typeof config.stepBase !== "undefined") ? Number(config.stepBase) : this._dateTypeConstructor.DefaultStepBase;
  3792. };
  3793.  
  3794. /**
  3795.  * @return {!Month}
  3796.  */
  3797. CalendarPicker.prototype.currentMonth = function() {
  3798.     return this._currentMonth;
  3799. };
  3800.  
  3801. /**
  3802.  * @enum {number}
  3803.  */
  3804. CalendarPicker.NavigationBehavior = {
  3805.     None: 0,
  3806.     WithAnimation: 1
  3807. };
  3808.  
  3809. /**
  3810.  * @param {!Month} month
  3811.  * @param {!CalendarPicker.NavigationBehavior} animate
  3812.  */
  3813. CalendarPicker.prototype.setCurrentMonth = function(month, behavior) {
  3814.     if (month > this.maximumMonth)
  3815.         month = this.maximumMonth;
  3816.     else if (month < this.minimumMonth)
  3817.         month = this.minimumMonth;
  3818.     if (this._currentMonth.equals(month))
  3819.         return;
  3820.     this._currentMonth = month;
  3821.     this.calendarTableView.scrollToMonth(this._currentMonth, behavior === CalendarPicker.NavigationBehavior.WithAnimation);
  3822.     this.adjustHeight();
  3823.     this.calendarTableView.setNeedsUpdateCells(true);
  3824.     this.dispatchEvent(CalendarPicker.EventTypeCurrentMonthChanged, {
  3825.         target: this
  3826.     });
  3827. };
  3828.  
  3829. CalendarPicker.prototype.adjustHeight = function() {
  3830.     var rowForFirstDayInMonth = this.calendarTableView.columnAndRowForDay(this._currentMonth.firstDay()).row;
  3831.     var rowForLastDayInMonth = this.calendarTableView.columnAndRowForDay(this._currentMonth.lastDay()).row;
  3832.     var numberOfRows = rowForLastDayInMonth - rowForFirstDayInMonth + 1;
  3833.     var calendarTableViewHeight = CalendarTableHeaderView.Height + numberOfRows * DayCell.Height + CalendarTableView.BorderWidth * 2;
  3834.     var height = (this.monthPopupView.isVisible ? YearListView.Height : calendarTableViewHeight) + CalendarHeaderView.Height + CalendarHeaderView.BottomMargin + CalendarPicker.Padding * 2 + CalendarPicker.BorderWidth * 2;
  3835.     this.setHeight(height);
  3836. };
  3837.  
  3838. CalendarPicker.prototype.selection = function() {
  3839.     return this._selection;
  3840. };
  3841.  
  3842. CalendarPicker.prototype.highlight = function() {
  3843.     return this._highlight;
  3844. };
  3845.  
  3846. /**
  3847.  * @return {!Day}
  3848.  */
  3849. CalendarPicker.prototype.firstVisibleDay = function() {
  3850.     var firstVisibleRow = this.calendarTableView.columnAndRowForDay(this.currentMonth().firstDay()).row;
  3851.     var firstVisibleDay = this.calendarTableView.dayAtColumnAndRow(0, firstVisibleRow);
  3852.     if (!firstVisibleDay)
  3853.         firstVisibleDay = Day.Minimum;
  3854.     return firstVisibleDay;
  3855. };
  3856.  
  3857. /**
  3858.  * @return {!Day}
  3859.  */
  3860. CalendarPicker.prototype.lastVisibleDay = function() { 
  3861.     var lastVisibleRow = this.calendarTableView.columnAndRowForDay(this.currentMonth().lastDay()).row;
  3862.     var lastVisibleDay = this.calendarTableView.dayAtColumnAndRow(DaysPerWeek - 1, lastVisibleRow);
  3863.     if (!lastVisibleDay)
  3864.         lastVisibleDay = Day.Maximum;
  3865.     return lastVisibleDay;
  3866. };
  3867.  
  3868. /**
  3869.  * @param {?Day} day
  3870.  */
  3871. CalendarPicker.prototype.selectRangeContainingDay = function(day) {
  3872.     var selection = day ? this._dateTypeConstructor.createFromDay(day) : null;
  3873.     this.setSelectionAndCommit(selection);
  3874. };
  3875.  
  3876. /**
  3877.  * @param {?Day} day
  3878.  */
  3879. CalendarPicker.prototype.highlightRangeContainingDay = function(day) {
  3880.     var highlight = day ? this._dateTypeConstructor.createFromDay(day) : null;
  3881.     this._setHighlight(highlight);
  3882. };
  3883.  
  3884. /**
  3885.  * Select the specified date.
  3886.  * @param {?DateType} dayOrWeekOrMonth
  3887.  */
  3888. CalendarPicker.prototype.setSelection = function(dayOrWeekOrMonth) {
  3889.     if (!this._selection && !dayOrWeekOrMonth)
  3890.         return;
  3891.     if (this._selection && this._selection.equals(dayOrWeekOrMonth))
  3892.         return;
  3893.     var firstDayInSelection = dayOrWeekOrMonth.firstDay();    
  3894.     var lastDayInSelection = dayOrWeekOrMonth.lastDay();
  3895.     var candidateCurrentMonth = Month.createFromDay(firstDayInSelection);
  3896.     if (this.firstVisibleDay() > lastDayInSelection || this.lastVisibleDay() < firstDayInSelection) {
  3897.         // Change current month if the selection is not visible at all.
  3898.         this.setCurrentMonth(candidateCurrentMonth, CalendarPicker.NavigationBehavior.WithAnimation);
  3899.     } else if (this.firstVisibleDay() < firstDayInSelection || this.lastVisibleDay() > lastDayInSelection) {
  3900.         // If the selection is partly visible, only change the current month if
  3901.         // doing so will make the whole selection visible.
  3902.         var firstVisibleRow = this.calendarTableView.columnAndRowForDay(candidateCurrentMonth.firstDay()).row;
  3903.         var firstVisibleDay = this.calendarTableView.dayAtColumnAndRow(0, firstVisibleRow);
  3904.         var lastVisibleRow = this.calendarTableView.columnAndRowForDay(candidateCurrentMonth.lastDay()).row;
  3905.         var lastVisibleDay = this.calendarTableView.dayAtColumnAndRow(DaysPerWeek - 1, lastVisibleRow);
  3906.         if (firstDayInSelection >= firstVisibleDay && lastDayInSelection <= lastVisibleDay)
  3907.             this.setCurrentMonth(candidateCurrentMonth, CalendarPicker.NavigationBehavior.WithAnimation);
  3908.     }
  3909.     this._setHighlight(dayOrWeekOrMonth);
  3910.     if (!this.isValid(dayOrWeekOrMonth))
  3911.         return;
  3912.     this._selection = dayOrWeekOrMonth;
  3913.     this.calendarTableView.setNeedsUpdateCells(true);
  3914. };
  3915.  
  3916. /**
  3917.  * Select the specified date, commit it, and close the popup.
  3918.  * @param {?DateType} dayOrWeekOrMonth
  3919.  */
  3920. CalendarPicker.prototype.setSelectionAndCommit = function(dayOrWeekOrMonth) {
  3921.     this.setSelection(dayOrWeekOrMonth);
  3922.     // Redraw the widget immidiately, and wait for some time to give feedback to
  3923.     // a user.
  3924.     this.element.offsetLeft;
  3925.     var value = this._selection.toString();
  3926.     if (CalendarPicker.commitDelayMs == 0) {
  3927.         // For testing.
  3928.         window.pagePopupController.setValueAndClosePopup(0, value);
  3929.     } else if (CalendarPicker.commitDelayMs < 0) {
  3930.         // For testing.
  3931.         window.pagePopupController.setValue(value);
  3932.     } else {
  3933.         setTimeout(function() {
  3934.             window.pagePopupController.setValueAndClosePopup(0, value);
  3935.         }, CalendarPicker.commitDelayMs);
  3936.     }
  3937. };
  3938.  
  3939. /**
  3940.  * @param {?DateType} dayOrWeekOrMonth
  3941.  */
  3942. CalendarPicker.prototype._setHighlight = function(dayOrWeekOrMonth) {
  3943.     if (!this._highlight && !dayOrWeekOrMonth)
  3944.         return;
  3945.     if (!dayOrWeekOrMonth && !this._highlight)
  3946.         return;
  3947.     if (this._highlight && this._highlight.equals(dayOrWeekOrMonth))
  3948.         return;
  3949.     this._highlight = dayOrWeekOrMonth;
  3950.     this.calendarTableView.setNeedsUpdateCells(true);
  3951. };
  3952.  
  3953. /**
  3954.  * @param {!number} value
  3955.  * @return {!boolean}
  3956.  */
  3957. CalendarPicker.prototype._stepMismatch = function(value) {
  3958.     var nextAllowedValue = Math.ceil((value - this.config.stepBase) / this.config.step) * this.config.step + this.config.stepBase;
  3959.     return nextAllowedValue >= value + this._dateTypeConstructor.DefaultStep;
  3960. };
  3961.  
  3962. /**
  3963.  * @param {!number} value
  3964.  * @return {!boolean}
  3965.  */
  3966. CalendarPicker.prototype._outOfRange = function(value) {
  3967.     return value < this.config.minimumValue || value > this.config.maximumValue;
  3968. };
  3969.  
  3970. /**
  3971.  * @param {!DateType} dayOrWeekOrMonth
  3972.  * @return {!boolean}
  3973.  */
  3974. CalendarPicker.prototype.isValid = function(dayOrWeekOrMonth) {
  3975.     var value = dayOrWeekOrMonth.valueOf();
  3976.     return dayOrWeekOrMonth instanceof this._dateTypeConstructor && !this._outOfRange(value) && !this._stepMismatch(value);
  3977. };
  3978.  
  3979. /**
  3980.  * @param {!Day} day
  3981.  * @return {!boolean}
  3982.  */
  3983. CalendarPicker.prototype.isValidDay = function(day) {
  3984.     return this.isValid(this._dateTypeConstructor.createFromDay(day));
  3985. };
  3986.  
  3987. /**
  3988.  * @param {!DateType} dateRange
  3989.  * @return {!boolean} Returns true if the highlight was changed.
  3990.  */
  3991. CalendarPicker.prototype._moveHighlight = function(dateRange) {
  3992.     if (!dateRange)
  3993.         return false;
  3994.     if (this._outOfRange(dateRange.valueOf()))
  3995.         return false;
  3996.     if (this.firstVisibleDay() > dateRange.middleDay() || this.lastVisibleDay() < dateRange.middleDay())
  3997.         this.setCurrentMonth(Month.createFromDay(dateRange.middleDay()), CalendarPicker.NavigationBehavior.WithAnimation);
  3998.     this._setHighlight(dateRange);
  3999.     return true;
  4000. };
  4001.  
  4002. /**
  4003.  * @param {?Event} event
  4004.  */
  4005. CalendarPicker.prototype.onCalendarTableKeyDown = function(event) {
  4006.     var key = event.keyIdentifier;
  4007.     var eventHandled = false;
  4008.     if (key == "U+0054") { // 't' key.
  4009.         this.selectRangeContainingDay(Day.createFromToday());
  4010.         eventHandled = true;
  4011.     } else if (key == "PageUp") {
  4012.         var previousMonth = this.currentMonth().previous();
  4013.         if (previousMonth && previousMonth >= this.config.minimumValue) {
  4014.             this.setCurrentMonth(previousMonth, CalendarPicker.NavigationBehavior.WithAnimation);
  4015.             eventHandled = true;
  4016.         }
  4017.     } else if (key == "PageDown") {
  4018.         var nextMonth = this.currentMonth().next();
  4019.         if (nextMonth && nextMonth >= this.config.minimumValue) {
  4020.             this.setCurrentMonth(nextMonth, CalendarPicker.NavigationBehavior.WithAnimation);
  4021.             eventHandled = true;
  4022.         }
  4023.     } else if (this._highlight) {
  4024.         if (global.params.isLocaleRTL ? key == "Right" : key == "Left") {
  4025.             eventHandled = this._moveHighlight(this._highlight.previous());
  4026.         } else if (key == "Up") {
  4027.             eventHandled = this._moveHighlight(this._highlight.previous(this.type === "date" ? DaysPerWeek : 1));
  4028.         } else if (global.params.isLocaleRTL ? key == "Left" : key == "Right") {
  4029.             eventHandled = this._moveHighlight(this._highlight.next());
  4030.         } else if (key == "Down") {
  4031.             eventHandled = this._moveHighlight(this._highlight.next(this.type === "date" ? DaysPerWeek : 1));
  4032.         } else if (key == "Enter") {
  4033.             this.setSelectionAndCommit(this._highlight);
  4034.         }
  4035.     } else if (key == "Left" || key == "Up" || key == "Right" || key == "Down") {
  4036.         // Highlight range near the middle.
  4037.         this.highlightRangeContainingDay(this.currentMonth().middleDay());
  4038.         eventHandled = true;
  4039.     }
  4040.  
  4041.     if (eventHandled) {
  4042.         event.stopPropagation();
  4043.         event.preventDefault();
  4044.     }
  4045. };
  4046.  
  4047. /**
  4048.  * @return {!number} Width in pixels.
  4049.  */
  4050. CalendarPicker.prototype.width = function() {
  4051.     return this.calendarTableView.width() + (CalendarTableView.BorderWidth + CalendarPicker.BorderWidth + CalendarPicker.Padding) * 2;
  4052. };
  4053.  
  4054. /**
  4055.  * @return {!number} Height in pixels.
  4056.  */
  4057. CalendarPicker.prototype.height = function() {
  4058.     return this._height;
  4059. };
  4060.  
  4061. /**
  4062.  * @param {!number} height Height in pixels.
  4063.  */
  4064. CalendarPicker.prototype.setHeight = function(height) {
  4065.     if (this._height === height)
  4066.         return;
  4067.     this._height = height;
  4068.     resizeWindow(this.width(), this._height);
  4069.     this.calendarTableView.setHeight(this._height - CalendarHeaderView.Height - CalendarHeaderView.BottomMargin - CalendarPicker.Padding * 2 - CalendarTableView.BorderWidth * 2);
  4070. };
  4071.  
  4072. /**
  4073.  * @param {?Event} event
  4074.  */
  4075. CalendarPicker.prototype.onBodyKeyDown = function(event) {
  4076.     var key = event.keyIdentifier;
  4077.     var eventHandled = false;
  4078.     var offset = 0;
  4079.     switch (key) {
  4080.     case "U+001B": // Esc key.
  4081.         window.pagePopupController.closePopup();
  4082.         eventHandled = true;
  4083.         break;
  4084.     case "U+004D": // 'm' key.
  4085.         offset = offset || 1; // Fall-through.
  4086.     case "U+0059": // 'y' key.
  4087.         offset = offset || MonthsPerYear; // Fall-through.
  4088.     case "U+0044": // 'd' key.
  4089.         offset = offset || MonthsPerYear * 10;
  4090.         var oldFirstVisibleRow = this.calendarTableView.columnAndRowForDay(this.currentMonth().firstDay()).row;
  4091.         this.setCurrentMonth(event.shiftKey ? this.currentMonth().previous(offset) : this.currentMonth().next(offset), CalendarPicker.NavigationBehavior.WithAnimation);
  4092.         var newFirstVisibleRow = this.calendarTableView.columnAndRowForDay(this.currentMonth().firstDay()).row;
  4093.         if (this._highlight) {
  4094.             var highlightMiddleDay = this._highlight.middleDay();
  4095.             this.highlightRangeContainingDay(highlightMiddleDay.next((newFirstVisibleRow - oldFirstVisibleRow) * DaysPerWeek));
  4096.         }
  4097.         eventHandled  =true;
  4098.         break;
  4099.     }
  4100.     if (eventHandled) {
  4101.         event.stopPropagation();
  4102.         event.preventDefault();
  4103.     }
  4104. };
  4105.  
  4106. if (window.dialogArguments) {
  4107.     initialize(dialogArguments);
  4108. } else {
  4109.     window.addEventListener("message", handleMessage, false);
  4110. }
  4111.